﻿unit GR32_Resamplers;

(* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1 or LGPL 2.1 with linking exception
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * Alternatively, the contents of this file may be used under the terms of the
 * Free Pascal modified version of the GNU Lesser General Public License
 * Version 2.1 (the "FPC modified LGPL License"), in which case the provisions
 * of this license are applicable instead of those above.
 * Please see the file LICENSE.txt for additional information concerning this
 * license.
 *
 * The Original Code is Graphics32
 *
 * The Initial Developers of the Original Code is
 * Mattias Andersson <mattias@centaurix.com>
 * (parts of this unit were taken from GR32_Transforms.pas by Alex A. Denisov)
 *
 * Many of the filters here were adapted from:
 * - "Interpolated Bitmap Resampling using filters"
 *   Anders Melander, 1997
 * which in turn was based on:
 * - "General Filtered Image Rescaling"
 *   Dale Schumacher
 *   Graphics Gems III, Academic Press, Inc.
 *   1 July 1992
 *
 * Portions created by the Initial Developer are Copyright (C) 2000-2009
 * the Initial Developer. All Rights Reserved.
 *
 * ***** END LICENSE BLOCK ***** *)

interface

{$include GR32.inc}

// Define PREMULTIPLY to have TKernelResampler handle alpha correctly.
// The downside of the alpha handling is that the performance and
// precision of the resampler suffers slightly.
{$define PREMULTIPLY}


uses
  Classes,
  SysUtils, // Exception
  GR32,
  GR32_Transforms,
  GR32_Containers,
  GR32_OrdinalMaps,
  GR32_Blend;

//------------------------------------------------------------------------------
//
//      BlockTransfer
//
//------------------------------------------------------------------------------
// Unscaled block transfer
//------------------------------------------------------------------------------
procedure BlockTransfer(
  Dst: TCustomBitmap32; DstX: Integer; DstY: Integer; DstClip: TRect;
  Src: TCustomBitmap32; SrcRect: TRect;
  CombineOp: TDrawMode; CombineCallBack: TPixelCombineEvent = nil);

procedure BlockTransferX(
  Dst: TCustomBitmap32; DstX, DstY: TFixed;
  Src: TCustomBitmap32; SrcRect: TRect;
  CombineOp: TDrawMode; CombineCallBack: TPixelCombineEvent = nil);


//------------------------------------------------------------------------------
//
//      StretchTransfer
//
//------------------------------------------------------------------------------
// Scaled block transfer using resampler
//------------------------------------------------------------------------------
procedure StretchTransfer(
  Dst: TCustomBitmap32; DstRect: TRect; DstClip: TRect;
  Src: TCustomBitmap32; SrcRect: TRect;
  Resampler: TCustomResampler;
  CombineOp: TDrawMode; CombineCallBack: TPixelCombineEvent = nil);


//------------------------------------------------------------------------------
//
//      BlendTransfer
//
//------------------------------------------------------------------------------
// Unscaled block blend
//------------------------------------------------------------------------------
procedure BlendTransfer(
  Dst: TCustomBitmap32; DstX, DstY: Integer; DstClip: TRect;
  SrcF: TCustomBitmap32; SrcRectF: TRect;
  SrcB: TCustomBitmap32; SrcRectB: TRect;
  BlendCallback: TBlendReg); overload;

procedure BlendTransfer(
  Dst: TCustomBitmap32; DstX, DstY: Integer; DstClip: TRect;
  SrcF: TCustomBitmap32; SrcRectF: TRect;
  SrcB: TCustomBitmap32; SrcRectB: TRect;
  BlendCallback: TBlendRegEx; MasterAlpha: Integer); overload;


//------------------------------------------------------------------------------
//
//      Resampling
//
//------------------------------------------------------------------------------
const
  MAX_KERNEL_WIDTH = 16;

type
  PKernelEntry = ^TKernelEntry;
  TKernelEntry = array [-MAX_KERNEL_WIDTH..MAX_KERNEL_WIDTH] of Integer;

  TArrayOfKernelEntry = array of TArrayOfInteger;
  PKernelEntryArray = ^TKernelEntryArray;
  TKernelEntryArray = array [0..0] of TArrayOfInteger;

  TFilterMethod = function(Value: TFloat): TFloat of object;

  EBitmapException = class(Exception);
  ESrcInvalidException = class(Exception);
  ENestedException = class(Exception);
  ETransformerException = class(Exception);

  TGetSampleInt = function(X, Y: Integer): TColor32 of object;
  TGetSampleFloat = function(X, Y: TFloat): TColor32 of object;
  TGetSampleFixed = function(X, Y: TFixed): TColor32 of object;


//------------------------------------------------------------------------------
//
//      TCustomKernel
//
//------------------------------------------------------------------------------
// Abstract base class for resampler kernels.
//------------------------------------------------------------------------------
type
  TCustomKernel = class(TPersistent)
  protected
    FObserver: TNotifiablePersistent;
  protected
    procedure AssignTo(Dst: TPersistent); override;
    function RangeCheck: Boolean; virtual;
  public
    constructor Create; virtual;
    procedure Changed;
    function Filter(Value: TFloat): TFloat; virtual; abstract;
    function GetWidth: TFloat; virtual; abstract;
    property Observer: TNotifiablePersistent read FObserver;
  end;

  TCustomKernelClass = class of TCustomKernel;


//------------------------------------------------------------------------------
//
//      TBoxKernel
//
//------------------------------------------------------------------------------
// Nearest neighbor interpolation filter.
// Also known as box filter, top-hat function or a Fourier window.
//------------------------------------------------------------------------------
type
  TBoxKernel = class(TCustomKernel)
  public
    function Filter(Value: TFloat): TFloat; override;
    function GetWidth: TFloat; override;
  end;


//------------------------------------------------------------------------------
//
//      TLinearKernel
//
//------------------------------------------------------------------------------
// Linear reconstruction filter.
// Also known as triangle filter, tent filter, roof function, Chateau function
// or a Bartlett window.
//------------------------------------------------------------------------------
type
  TLinearKernel = class(TCustomKernel)
  public
    function Filter(Value: TFloat): TFloat; override;
    function GetWidth: TFloat; override;
  end;


//------------------------------------------------------------------------------
//
//      TCosineKernel
//
//------------------------------------------------------------------------------
// Cosine reconstruction filter.
//------------------------------------------------------------------------------
type
  TCosineKernel = class(TCustomKernel)
  public
    function Filter(Value: TFloat): TFloat; override;
    function GetWidth: TFloat; override;
  end;


//------------------------------------------------------------------------------
//
//      TSplineKernel
//
//------------------------------------------------------------------------------
// B-Spline interpolation filter.
// Not the same as the Spline windowed Sinc kernel.
//------------------------------------------------------------------------------
type
  TSplineKernel = class(TCustomKernel)
  protected
    function RangeCheck: Boolean; override;
  public
    function Filter(Value: TFloat): TFloat; override;
    function GetWidth: TFloat; override;
  end;


//------------------------------------------------------------------------------
//
//      TMitchellKernel
//
//------------------------------------------------------------------------------
// An implementation of a special case of the cubic filter described by Mitchell
// and Netravali using the parameters (B: 1/3, C: 1/3).
//
// References:
//
// - Don P. Mitchell & Arun N. Netravali
//   AT&T Bell Laboratories
//   "Reconstruction Filters in Computer Graphics"
//   Computer Graphics, Volume 22, Number 4, August 1988.
//
// Also known as Mitchell-Netravali.
// Many other variants of this filter, with various other values for B&C, exist.
// Often people come up with some variation of B&C and then put their own name
// on the filter. For example Robidoux (B:0.3782, C:0.3109), etc.
//
//------------------------------------------------------------------------------
type
  TMitchellKernel = class(TCustomKernel)
  protected
    function RangeCheck: Boolean; override;
  public
    function Filter(Value: TFloat): TFloat; override;
    function GetWidth: TFloat; override;
  end;


//------------------------------------------------------------------------------
//
//      TCubicKernel
//
//------------------------------------------------------------------------------
// A reconstruction filter described by a cubic polynomial.
//
// References:
//
// - Robert G. Keys
//   "Cubic convolution interpolation for digital image processing"
//   IEEE Transactions on Acoustics, Speech, and Signal Processing
//   Volume: 29, Issue: 6, December 1981
//
//------------------------------------------------------------------------------
type
  TCubicKernel = class(TCustomKernel)
  private
    FCoeff: TFloat;
    procedure SetCoeff(const Value: TFloat);
  protected
    function RangeCheck: Boolean; override;
  public
    constructor Create; override;
    function Filter(Value: TFloat): TFloat; override;
    function GetWidth: TFloat; override;
  published
    property Coeff: TFloat read FCoeff write SetCoeff;
  end;


//------------------------------------------------------------------------------
//
//      THermiteKernel
//
//------------------------------------------------------------------------------
// An implementation of the hermite kernel.
//------------------------------------------------------------------------------
type
  THermiteKernel = class(TCustomKernel)
  private
    FBias: TFloat;
    FTension: TFloat;
    procedure SetBias(const Value: TFloat);
    procedure SetTension(const Value: TFloat);
  protected
    function RangeCheck: Boolean; override;
  public
    constructor Create; override;
    function Filter(Value: TFloat): TFloat; override;
    function GetWidth: TFloat; override;
  published
    property Bias: TFloat read FBias write SetBias;
    property Tension: TFloat read FTension write SetTension;
  end;


//------------------------------------------------------------------------------
//
//      TSinshKernel
//
//------------------------------------------------------------------------------
// A filter described by a hyperbolic sine, something, something.
//------------------------------------------------------------------------------
type
  TSinshKernel = class(TCustomKernel)
  private
    FWidth: TFloat;
    FCoeff: TFloat;
    procedure SetCoeff(const Value: TFloat);
  protected
    function RangeCheck: Boolean; override;
  public
    constructor Create; override;
    procedure SetWidth(Value: TFloat);
    function GetWidth: TFloat; override;
    function Filter(Value: TFloat): TFloat; override;
  published
    property Coeff: TFloat read FCoeff write SetCoeff;
    property Width: TFloat read GetWidth write SetWidth;
  end;


//------------------------------------------------------------------------------
//
//      TWindowedKernel
//
//------------------------------------------------------------------------------
// Abstract base class for windowed kernels.
// Returns the value of the filter function constrained by a window function.
// Descendant classes must override the Window method in order to implement a
// custom window function.
//------------------------------------------------------------------------------
type
  TWindowedKernel = class(TCustomKernel)
  strict protected
    FWidth : TFloat;
    FWidthReciprocal : TFloat;
  protected
    function RangeCheck: Boolean; override;
    function Window(Value: TFloat): TFloat; virtual; abstract;
    procedure DoSetWidth(Value: TFloat);
  public
    function Filter(Value: TFloat): TFloat; override;
    procedure SetWidth(Value: TFloat);
    function GetWidth: TFloat; override;
    property WidthReciprocal : TFloat read FWidthReciprocal;
  published
    property Width: TFloat read FWidth write SetWidth;
  end;


//------------------------------------------------------------------------------
//
//      TGaussianKernel
//
//------------------------------------------------------------------------------
// A kernel constrained by a Gaussian window function.
//------------------------------------------------------------------------------
type
  TGaussianKernel = class(TWindowedKernel)
  private
    FSigma: TFloat;
    FSigmaReciprocal: TFloat;
    FNormalizationFactor: Single;
    procedure DoSetSigma(const Value: TFloat);
    procedure SetSigma(const Value: TFloat);
  protected
    function Window(Value: TFloat): TFloat; override;
  public
    constructor Create; override;
  published
    property Sigma: TFloat read FSigma write SetSigma;
  end;

//------------------------------------------------------------------------------
//
//      TWindowedSincKernel
//
//------------------------------------------------------------------------------
// Abstract base class for windowed Sinc kernels.
// Returns the value of the Sinc function constrained by a window function.
// Descendant classes must override the Window method in order to implement a
// custom window function.
//------------------------------------------------------------------------------
type
  TWindowedSincKernel = class(TWindowedKernel)
  protected
    class function Sinc(Value: TFloat): TFloat; static;
  public
    constructor Create; override;
    function Filter(Value: TFloat): TFloat; override;
  published
    property Width: TFloat read FWidth write SetWidth;
  end;


//------------------------------------------------------------------------------
//
//      TAlbrecht-Kernel
//
//------------------------------------------------------------------------------
// A Sinc kernel constrained by Albrecht window functions.
//
// References:
//
// - Hans-Helge Albrecht
//   Physikalisch-Technische Bundesanstalt, Berlin, Germany
//   "A family of cosine-sum windows for high resolution measurements"
//   IEEE International Conference on Acoustics, Speech, and Signal Processing,
//   Salt Lake City, May 2001.
//
//------------------------------------------------------------------------------
type
  TAlbrechtKernel = class(TWindowedSincKernel)
  private
    FTerms: Integer;
    FCoefPointer : Array [0..11] of Double;
    procedure SetTerms(Value : Integer);
  protected
    function Window(Value: TFloat): TFloat; override;
  public
    constructor Create; override;
  published
    property Terms: Integer read FTerms write SetTerms;
  end;


//------------------------------------------------------------------------------
//
//      TLanczosKernel
//
//------------------------------------------------------------------------------
// A Sinc kernel constrained by a Lanczos window function.
// It uses three lobes of the Sinc filter as a window.
//
// References:
//
// - Claude E. Duchon
//   School of Meteorology, University of Oklahoma, USA
//   "Lanczos Filtering in One and Two Dimensions"
//   Journal of Applied Meteorology and Climatology, volume 18, pp. 1016-1022
//   1 Aug 1979
//
// Also known as Lanczo3.
//------------------------------------------------------------------------------
type
  TLanczosKernel = class(TWindowedSincKernel)
  protected
    function Window(Value: TFloat): TFloat; override;
  end;


//------------------------------------------------------------------------------
//
//      TBlackmanKernel
//
//------------------------------------------------------------------------------
// A Sinc kernel constrained by a Blackman window function.
//
// References:
//
// - Ralph Beebe Blackman & John Wilder Tukey
//   "Particular Pairs of Windows"
//   The measurement of power spectra from the point of view of communications
//   engineering.
//   New York: Dover, pp. 98-99, 1959.
//
//------------------------------------------------------------------------------
type
  TBlackmanKernel = class(TWindowedSincKernel)
  protected
    function Window(Value: TFloat): TFloat; override;
  end;


//------------------------------------------------------------------------------
//
//      THannKernel
//
//------------------------------------------------------------------------------
// A Sinc kernel constrained by a Hann window function.
// Also known as raised cosine.
//
// References:
//
// - Ralph Beebe Blackman & John Wilder Tukey
//   The measurement of power spectra from the point of view of communications
//   engineering — Part I.
//   The Bell System Technical Journal. 37 (1), pp. 273, 1958.
//
// Supposedly based on work done by Julius von Hann, 1839-1921
//
//------------------------------------------------------------------------------
type
  THannKernel = class(TWindowedSincKernel)
  protected
    function Window(Value: TFloat): TFloat; override;
  end;


//------------------------------------------------------------------------------
//
//      THammingKernel
//
//------------------------------------------------------------------------------
// A Sinc kernel constrained by a Hamming window function.
//
// References:
//
// - Richard W. Hamming
//   "Digital Filters"
//   Prentice-Hall, 1977 pp. 226; 2nd ed. 1983; 3rd ed. 1989
//
//------------------------------------------------------------------------------
type
  THammingKernel = class(TWindowedSincKernel)
  protected
    function Window(Value: TFloat): TFloat; override;
  end;


//------------------------------------------------------------------------------
//
//      TNearestResampler
//
//------------------------------------------------------------------------------
// A fast resampler based on the nearest-neighbor interpolation algorithm.
//------------------------------------------------------------------------------
type
  TNearestResampler = class(TCustomResampler)
  private
    FGetSampleInt: TGetSampleInt;
  protected
    function GetPixelTransparentEdge(X, Y: Integer): TColor32;
    function GetWidth: TFloat; override;
    procedure Resample(
      Dst: TCustomBitmap32; DstRect: TRect; DstClip: TRect;
      Src: TCustomBitmap32; SrcRect: TRect;
      CombineOp: TDrawMode; CombineCallBack: TPixelCombineEvent); override;
  public
    function GetSampleInt(X, Y: Integer): TColor32; override;
    function GetSampleFixed(X, Y: TFixed): TColor32; override;
    function GetSampleFloat(X, Y: TFloat): TColor32; override;
    procedure PrepareSampling; override;
  end;


//------------------------------------------------------------------------------
//
//      TLinearResampler
//
//------------------------------------------------------------------------------
// Performance-optimized linear upsampler.
// Falls back to using TLinearKernel for downsampling.
//------------------------------------------------------------------------------
type
  TLinearResampler = class(TCustomResampler)
  private
    FLinearKernel: TLinearKernel;
    FGetSampleFixed: TGetSampleFixed;
  protected
    function GetWidth: TFloat; override;
    function GetPixelTransparentEdge(X, Y: TFixed): TColor32;
    procedure Resample(
      Dst: TCustomBitmap32; DstRect: TRect; DstClip: TRect;
      Src: TCustomBitmap32; SrcRect: TRect;
      CombineOp: TDrawMode; CombineCallBack: TPixelCombineEvent); override;
  public
    constructor Create; override;
    destructor Destroy; override;
    function GetSampleFixed(X, Y: TFixed): TColor32; override;
    function GetSampleFloat(X, Y: TFloat): TColor32; override;
    procedure PrepareSampling; override;
  end;


//------------------------------------------------------------------------------
//
//      TDraftResampler
//
//------------------------------------------------------------------------------
// Performance-optimized downsampler.
// Falls back to using TLinearResampler for upsampling.
//------------------------------------------------------------------------------
type
  TDraftResampler = class(TLinearResampler)
  protected
    procedure Resample(
      Dst: TCustomBitmap32; DstRect: TRect; DstClip: TRect;
      Src: TCustomBitmap32; SrcRect: TRect;
      CombineOp: TDrawMode; CombineCallBack: TPixelCombineEvent); override;
  end;


//------------------------------------------------------------------------------
//
//      TKernelResampler
//
//------------------------------------------------------------------------------
// This resampler class will perform resampling by using an arbitrary
// reconstruction kernel.
// By using the kmTableNearest and kmTableLinear kernel modes, kernel values are
// precomputed in a look-up table. This allows GetSample to execute faster for
// complex kernels.
//------------------------------------------------------------------------------
type
  TKernelMode = (kmDynamic, kmTableNearest, kmTableLinear);

  TKernelResampler = class(TCustomResampler)
  private
    FKernel: TCustomKernel;
    FKernelMode: TKernelMode;
    FWeightTable: TIntegerMap;
    FTableSize: Integer;
    FOuterColor: TColor32;
    procedure SetKernel(const Value: TCustomKernel);
    function GetKernelClassName: string;
    procedure SetKernelClassName(const Value: string);
    procedure SetKernelMode(const Value: TKernelMode);
    procedure SetTableSize(Value: Integer);
  protected
    function GetWidth: TFloat; override;
  public
    constructor Create; override;
    destructor Destroy; override;
    function GetSampleFloat(X, Y: TFloat): TColor32; override;
    procedure Resample(
      Dst: TCustomBitmap32; DstRect: TRect; DstClip: TRect;
      Src: TCustomBitmap32; SrcRect: TRect;
      CombineOp: TDrawMode; CombineCallBack: TPixelCombineEvent); override;
    procedure PrepareSampling; override;
    procedure FinalizeSampling; override;
  published
    property KernelClassName: string read GetKernelClassName write SetKernelClassName;
    property Kernel: TCustomKernel read FKernel write SetKernel;
    property KernelMode: TKernelMode read FKernelMode write SetKernelMode;
    property TableSize: Integer read FTableSize write SetTableSize;
  end;


//------------------------------------------------------------------------------
//
//      TNestedSampler
//
//------------------------------------------------------------------------------
// TNestedSampler is a base class for chained or nested samplers.
//------------------------------------------------------------------------------
type
  TNestedSampler = class(TCustomSampler)
  private
    FSampler: TCustomSampler;
    FGetSampleInt: TGetSampleInt;
    FGetSampleFixed: TGetSampleFixed;
    FGetSampleFloat: TGetSampleFloat;
    procedure SetSampler(const Value: TCustomSampler);
  protected
    procedure AssignTo(Dst: TPersistent); override;
  public
    constructor Create(ASampler: TCustomSampler); reintroduce; virtual;
    procedure PrepareSampling; override;
    procedure FinalizeSampling; override;
    function HasBounds: Boolean; override;
    function GetSampleBounds: TFloatRect; override;
  published
    property Sampler: TCustomSampler read FSampler write SetSampler;
  end;


//------------------------------------------------------------------------------
//
//      TTransformer
//
//------------------------------------------------------------------------------
// TTransformer is a nested sampler that will transform the sampling coordinates
// using a transformation defined by a TTransformation descendant.
//------------------------------------------------------------------------------
type
  TTransformInt = procedure(DstX, DstY: Integer; out SrcX, SrcY: Integer) of object;
  TTransformFixed = procedure(DstX, DstY: TFixed; out SrcX, SrcY: TFixed) of object;
  TTransformFloat = procedure(DstX, DstY: TFloat; out SrcX, SrcY: TFloat) of object;

  TTransformer = class(TNestedSampler)
  private
    FTransformation: TTransformation;
    FTransformInt: TTransformInt; // Unused
    FTransformFixed: TTransformFixed;
    FTransformFloat: TTransformFloat;
    FReverse: boolean;
  public
    constructor Create(ASampler: TCustomSampler; ATransformation: TTransformation; AReverse: boolean = True); reintroduce;
    procedure PrepareSampling; override;
    function GetSampleInt(X, Y: Integer): TColor32; override;
    function GetSampleFixed(X, Y: TFixed): TColor32; override;
    function GetSampleFloat(X, Y: TFloat): TColor32; override;
    function HasBounds: Boolean; override;
    function GetSampleBounds: TFloatRect; override;
  published
    property Transformation: TTransformation read FTransformation write FTransformation;
    property ReverseTransform: boolean read FReverse write FReverse;
  end;


//------------------------------------------------------------------------------
//
//      TSuperSampler
//
//------------------------------------------------------------------------------
// TSuperSampler is a nested sampler that adds a mechanism for performing super
// sampling.
//------------------------------------------------------------------------------
type
  TSamplingRange = 1..MaxInt;

  TSuperSampler = class(TNestedSampler)
  private
    FSamplingY: TSamplingRange;
    FSamplingX: TSamplingRange;
    FDistanceX: TFixed;
    FDistanceY: TFixed;
    FOffsetX: TFixed;
    FOffsetY: TFixed;
    FScale: TFixed;
    procedure SetSamplingX(const Value: TSamplingRange);
    procedure SetSamplingY(const Value: TSamplingRange);
  public
    constructor Create(Sampler: TCustomSampler); override;
    function GetSampleFixed(X, Y: TFixed): TColor32; override;
  published
    property SamplingX: TSamplingRange read FSamplingX write SetSamplingX;
    property SamplingY: TSamplingRange read FSamplingY write SetSamplingY;
  end;


//------------------------------------------------------------------------------
//
//      TAdaptiveSuperSampler
//
//------------------------------------------------------------------------------
// Adaptive supersampling is different from ordinary supersampling in the sense
// that samples are choosen adaptively; It is a recursive method that collects
// more samples at areas with rapid transitions.
//------------------------------------------------------------------------------
type
  TRecurseProc = function(X, Y, W: TFixed; const C1, C2: TColor32): TColor32 of object;

  TAdaptiveSuperSampler = class(TNestedSampler)
  private
    FMinOffset: TFixed;
    FLevel: Integer;
    FTolerance: Integer;
    procedure SetLevel(const Value: Integer);
    function DoRecurse(X, Y, Offset: TFixed; const A, B, C, D, E: TColor32): TColor32;
    function QuadrantColor(const C1, C2: TColor32; X, Y, Offset: TFixed;
      Proc: TRecurseProc): TColor32;
    function RecurseAC(X, Y, Offset: TFixed; const A, C: TColor32): TColor32;
    function RecurseBD(X, Y, Offset: TFixed; const B, D: TColor32): TColor32;
  protected
    function CompareColors(C1, C2: TColor32): Boolean; virtual;
  public
    constructor Create(Sampler: TCustomSampler); override;
    function GetSampleFixed(X, Y: TFixed): TColor32; override;
  published
    property Level: Integer read FLevel write SetLevel;
    property Tolerance: Integer read FTolerance write FTolerance;
  end;


//------------------------------------------------------------------------------
//
//      TPatternSampler
//
//------------------------------------------------------------------------------
// TPatternSampler provides a mechanism for performing sampling according to a
// supplied sample pattern.
//------------------------------------------------------------------------------
type
  TFloatSamplePattern = array of array of TArrayOfFloatPoint;
  TFixedSamplePattern = array of array of TArrayOfFixedPoint;

  TPatternSampler = class(TNestedSampler)
  private
    FPattern: TFixedSamplePattern;
    procedure SetPattern(const Value: TFixedSamplePattern);
  protected
    WrapProcVert: TWrapProc;
  public
    destructor Destroy; override;
    function GetSampleFixed(X, Y: TFixed): TColor32; override;
    property Pattern: TFixedSamplePattern read FPattern write SetPattern;
  end;

  { Auxiliary record used in accumulation routines }
  PBufferEntry = ^TBufferEntry;
  TBufferEntry = record
    B, G, R, A: Integer;
  end;


//------------------------------------------------------------------------------
//
//      TKernelSampler
//
//------------------------------------------------------------------------------
// TKernelSampler is an abstract base class for samplers that compute an output
// sample by collecting a number of samples in a local region of the actual
// sample coordinate.
//------------------------------------------------------------------------------
type
  TKernelSampler = class(TNestedSampler)
  private
    FKernel: TIntegerMap;
    FStartEntry: TBufferEntry;
    FCenterX: Integer;
    FCenterY: Integer;
  protected
    procedure SetKernel(const Value: TIntegerMap);
    procedure UpdateBuffer(var Buffer: TBufferEntry; Color: TColor32;
      Weight: Integer); virtual; abstract;
    function ConvertBuffer(var Buffer: TBufferEntry): TColor32; virtual;
  public
    constructor Create(ASampler: TCustomSampler); override;
    destructor Destroy; override;
    function GetSampleInt(X, Y: Integer): TColor32; override;
    function GetSampleFixed(X, Y: TFixed): TColor32; override;
  published
    property Kernel: TIntegerMap read FKernel write SetKernel;
    property CenterX: Integer read FCenterX write FCenterX;
    property CenterY: Integer read FCenterY write FCenterY;
  end;


//------------------------------------------------------------------------------
//
//      TConvolver
//
//------------------------------------------------------------------------------
// The TConvolver kernel sampler provides functionality for performing discrete
// convolution within a chain of nested samplers.
//------------------------------------------------------------------------------
type
  TConvolver = class(TKernelSampler)
  protected
    procedure UpdateBuffer(var Buffer: TBufferEntry; Color: TColor32;
      Weight: Integer); override;
  end;


//------------------------------------------------------------------------------
//
//      TSelectiveConvolver
//
//------------------------------------------------------------------------------
// TSelectiveConvolver works similarly to TConvolver, but it will exclude color
// samples from the convolution depending on a the difference from a local
// reference sample value.
//------------------------------------------------------------------------------
type
  TSelectiveConvolver = class(TConvolver)
  private
    FRefColor: TColor32;
    FDelta: Integer;
    FWeightSum: TBufferEntry;
  protected
    procedure UpdateBuffer(var Buffer: TBufferEntry; Color: TColor32;
      Weight: Integer); override;
    function ConvertBuffer(var Buffer: TBufferEntry): TColor32; override;
  public
    constructor Create(ASampler: TCustomSampler); override;
    function GetSampleInt(X, Y: Integer): TColor32; override;
    function GetSampleFixed(X, Y: TFixed): TColor32; override;
  published
    property Delta: Integer read FDelta write FDelta;
  end;


//------------------------------------------------------------------------------
//
//      TMorphologicalSampler
//
//------------------------------------------------------------------------------
// Abstract base class for TDilater and TEroder.
//------------------------------------------------------------------------------
type
  TMorphologicalSampler = class(TKernelSampler)
  protected
    function ConvertBuffer(var Buffer: TBufferEntry): TColor32; override;
  end;


//------------------------------------------------------------------------------
//
//      TDilater
//
//------------------------------------------------------------------------------
// TDilater is a nested sampler for performing morphological dilation.
//------------------------------------------------------------------------------
type
  TDilater = class(TMorphologicalSampler)
  protected
    procedure UpdateBuffer(var Buffer: TBufferEntry; Color: TColor32;
      Weight: Integer); override;
  end;


//------------------------------------------------------------------------------
//
//      TEroder
//
//------------------------------------------------------------------------------
// TEroder is a nested sampler for performing morphological erosion
//------------------------------------------------------------------------------
type
  TEroder = class(TMorphologicalSampler)
  protected
    procedure UpdateBuffer(var Buffer: TBufferEntry; Color: TColor32;
      Weight: Integer); override;
  public
    constructor Create(ASampler: TCustomSampler); override;
  end;


//------------------------------------------------------------------------------
//
//      TExpander
//
//------------------------------------------------------------------------------
// TExpander implements a neighborhood operation similar to morphological
// dilation.
//------------------------------------------------------------------------------
type
  TExpander = class(TKernelSampler)
  protected
    procedure UpdateBuffer(var Buffer: TBufferEntry; Color: TColor32;
      Weight: Integer); override;
  end;


//------------------------------------------------------------------------------
//
//      TContracter
//
//------------------------------------------------------------------------------
// Similar to TExpander, but contracts instead of exanding.
//------------------------------------------------------------------------------
type
  TContracter = class(TExpander)
  private
    FMaxWeight: TColor32;
  protected
    procedure UpdateBuffer(var Buffer: TBufferEntry; Color: TColor32;
      Weight: Integer); override;
  public
    procedure PrepareSampling; override;
    function GetSampleInt(X, Y: Integer): TColor32; override;
    function GetSampleFixed(X, Y: TFixed): TColor32; override;
  end;


//------------------------------------------------------------------------------
//
//      CreateJitteredPattern
//
//------------------------------------------------------------------------------
// Create a random jitter pattern for use with TPatternSampler.
//------------------------------------------------------------------------------
function CreateJitteredPattern(TileWidth, TileHeight, SamplesX, SamplesY: Integer): TFixedSamplePattern;


//------------------------------------------------------------------------------
//
//      Convolution and morphological routines
//
//------------------------------------------------------------------------------
// Kernel sampler wrapper functions.
//------------------------------------------------------------------------------
procedure Convolve(Src, Dst: TCustomBitmap32; Kernel: TIntegerMap; CenterX, CenterY: Integer);
procedure Dilate(Src, Dst: TCustomBitmap32; Kernel: TIntegerMap; CenterX, CenterY: Integer);
procedure Erode(Src, Dst: TCustomBitmap32; Kernel: TIntegerMap; CenterX, CenterY: Integer);
procedure Expand(Src, Dst: TCustomBitmap32; Kernel: TIntegerMap; CenterX, CenterY: Integer);
procedure Contract(Src, Dst: TCustomBitmap32; Kernel: TIntegerMap; CenterX, CenterY: Integer);


//------------------------------------------------------------------------------
//
//      Auxiliary routines for accumulating colors in a buffer
//
//------------------------------------------------------------------------------
procedure IncBuffer(var Buffer: TBufferEntry; Color: TColor32); {$IFDEF USEINLINING} inline; {$ENDIF}
procedure MultiplyBuffer(var Buffer: TBufferEntry; W: Integer); {$IFDEF USEINLINING} inline; {$ENDIF}
function BufferToColor32(const Buffer: TBufferEntry; Shift: Integer): TColor32; {$IFDEF USEINLINING} inline; {$ENDIF}
procedure ShrBuffer(var Buffer: TBufferEntry; Shift: Integer); {$IFDEF USEINLINING} inline; {$ENDIF}


//------------------------------------------------------------------------------
//
//      Downsample byte map
//
//------------------------------------------------------------------------------
procedure DownsampleByteMap2x(Source, Dest: TByteMap);
procedure DownsampleByteMap3x(Source, Dest: TByteMap);
procedure DownsampleByteMap4x(Source, Dest: TByteMap);


//------------------------------------------------------------------------------
//
//      Registration routines
//
//------------------------------------------------------------------------------
procedure RegisterResampler(ResamplerClass: TCustomResamplerClass);
procedure RegisterKernel(KernelClass: TCustomKernelClass);

var
  KernelList: TClassList;
  ResamplerList: TClassList;

const
  EMPTY_ENTRY: TBufferEntry = (B: 0; G: 0; R: 0; A: 0) deprecated 'Use Default(TBufferEntry)';


//------------------------------------------------------------------------------
//
//      Bindings
//
//------------------------------------------------------------------------------
var
  BlockAverage: function(Dlx, Dly: Cardinal; RowSrc: PColor32; OffSrc: Cardinal): TColor32;
  Interpolator: function(WX_256, WY_256: Cardinal; C11, C21: PColor32): TColor32;


//------------------------------------------------------------------------------

resourcestring
  SDstNil = 'Destination bitmap is nil';
  SSrcNil = 'Source bitmap is nil';
  SSrcInvalid = 'Source rectangle is invalid';
  SSamplerNil = 'Nested sampler is nil';
  STransformationNil = 'Transformation is nil';

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------

implementation

uses
  Math,
  Types,
  GR32_Bindings,
  GR32_LowLevel,
  GR32_Rasterizers,
  GR32_Math,
  GR32_Gamma;

resourcestring
  RCStrInvalidSrcRect = 'Invalid SrcRect';

const
  CAlbrecht2 : array [0..1] of Double = (5.383553946707251E-1,
    4.616446053292749E-1);
  CAlbrecht3 : array [0..2] of Double = (3.46100822018625E-1,
    4.97340635096738E-1, 1.56558542884637E-1);
  CAlbrecht4 : array [0..3] of Double = (2.26982412792069E-1,
    4.57254070828427E-1, 2.73199027957384E-1, 4.25644884221201E-2);
  CAlbrecht5 : array [0..4] of Double = (1.48942606015830E-1,
    3.86001173639176E-1, 3.40977403214053E-1, 1.139879604246E-1,
    1.00908567063414E-2);
  CAlbrecht6 : array [0..5] of Double = (9.71676200107429E-2,
    3.08845222524055E-1, 3.62623371437917E-1, 1.88953325525116E-1,
    4.02095714148751E-2, 2.20088908729420E-3);
  CAlbrecht7 : array [0..6] of Double = (6.39644241143904E-2,
    2.39938645993528E-1, 3.50159563238205E-1, 2.47741118970808E-1,
    8.54382560558580E-2, 1.23202033692932E-2, 4.37788257917735E-4);
  CAlbrecht8 : array [0..7] of Double = (4.21072107042137E-2,
    1.82076226633776E-1, 3.17713781059942E-1, 2.84438001373442E-1,
    1.36762237777383E-1, 3.34038053504025E-2, 3.41677216705768E-3,
    8.19649337831348E-5);
  CAlbrecht9 : array [0..8] of Double = (2.76143731612611E-2,
    1.35382228758844E-1, 2.75287234472237E-1, 2.98843335317801E-1,
    1.85319330279284E-1, 6.48884482549063E-2, 1.17641910285655E-2,
    8.85987580106899E-4, 1.48711469943406E-5);
  CAlbrecht10: array [0..9] of Double = (1.79908225352538E-2,
    9.87959586065210E-2, 2.29883817001211E-1, 2.94113019095183E-1,
    2.24338977814325E-1, 1.03248806248099E-1, 2.75674109448523E-2,
    3.83958622947123E-3, 2.18971708430106E-4, 2.62981665347889E-6);
  CAlbrecht11: array [0..10] of Double = (1.18717127796602E-2,
    7.19533651951142E-2, 1.87887160922585E-1, 2.75808174097291E-1,
    2.48904243244464E-1, 1.41729867200712E-1, 5.02002976228256E-2,
    1.04589649084984E-2, 1.13615112741660E-3, 4.96285981703436E-5,
    4.34303262685720E-7);

type
  TTransformationAccess = class(TTransformation);
  TCustomBitmap32Access = class(TCustomBitmap32);
  TCustomResamplerAccess = class(TCustomResampler);
  TCustomKernelAccess = class(TCustomKernel);

  TPointRec = record
    Pos: Integer;
    Weight: Integer;
  end;

  TCluster = array of TPointRec;
  TMappingTable = array of TCluster;

  TKernelSamplerClass = class of TKernelSampler;

{ Auxiliary rasterization routine for kernel-based samplers }
procedure RasterizeKernelSampler(Src, Dst: TCustomBitmap32; Kernel: TIntegerMap;
  CenterX, CenterY: Integer; SamplerClass: TKernelSamplerClass);
var
  Sampler: TKernelSampler;
  Rasterizer: TRasterizer;
begin
  Rasterizer := DefaultRasterizerClass.Create;
  try
    Dst.SetSizeFrom(Src);
    Sampler := SamplerClass.Create(Src.Resampler);
    try
      Sampler.Kernel := Kernel;
      Sampler.CenterX := CenterX;
      Sampler.CenterY := CenterY;

      Rasterizer.Sampler := Sampler;
      Rasterizer.Rasterize(Dst);
    finally
      Sampler.Free;
    end;
  finally
    Rasterizer.Free;
  end;
end;

procedure Convolve(Src, Dst: TCustomBitmap32; Kernel: TIntegerMap; CenterX, CenterY: Integer);
begin
  RasterizeKernelSampler(Src, Dst, Kernel, CenterX, CenterY, TConvolver);
end;

procedure Dilate(Src, Dst: TCustomBitmap32; Kernel: TIntegerMap; CenterX, CenterY: Integer);
begin
  RasterizeKernelSampler(Src, Dst, Kernel, CenterX, CenterY, TDilater);
end;

procedure Erode(Src, Dst: TCustomBitmap32; Kernel: TIntegerMap; CenterX, CenterY: Integer);
begin
  RasterizeKernelSampler(Src, Dst, Kernel, CenterX, CenterY, TEroder);
end;

procedure Expand(Src, Dst: TCustomBitmap32; Kernel: TIntegerMap; CenterX, CenterY: Integer);
begin
  RasterizeKernelSampler(Src, Dst, Kernel, CenterX, CenterY, TExpander);
end;

procedure Contract(Src, Dst: TCustomBitmap32; Kernel: TIntegerMap; CenterX, CenterY: Integer);
begin
  RasterizeKernelSampler(Src, Dst, Kernel, CenterX, CenterY, TContracter);
end;

{ Auxiliary routines }

procedure IncBuffer(var Buffer: TBufferEntry; Color: TColor32);
begin
  with TColor32Entry(Color) do
  begin
    Inc(Buffer.B, B);
    Inc(Buffer.G, G);
    Inc(Buffer.R, R);
    Inc(Buffer.A, A);
  end;
end;

procedure MultiplyBuffer(var Buffer: TBufferEntry; W: Integer);
begin
  Buffer.B := Buffer.B * W;
  Buffer.G := Buffer.G * W;
  Buffer.R := Buffer.R * W;
  Buffer.A := Buffer.A * W;
end;

procedure ShrBuffer(var Buffer: TBufferEntry; Shift: Integer);
begin
  Buffer.B := Buffer.B shr Shift;
  Buffer.G := Buffer.G shr Shift;
  Buffer.R := Buffer.R shr Shift;
  Buffer.A := Buffer.A shr Shift;
end;

function BufferToColor32(const Buffer: TBufferEntry; Shift: Integer): TColor32;
begin
  with TColor32Entry(Result) do
  begin
    B := Buffer.B shr Shift;
    G := Buffer.G shr Shift;
    R := Buffer.R shr Shift;
    A := Buffer.A shr Shift;
  end;
end;

procedure CheckBitmaps(Dst, Src: TCustomBitmap32); {$IFDEF USEINLINING}inline;{$ENDIF}
begin
  if not Assigned(Dst) then raise EBitmapException.Create(SDstNil);
  if not Assigned(Src) then raise EBitmapException.Create(SSrcNil);
end;

procedure BlendBlock(
  Dst: TCustomBitmap32; DstRect: TRect;
  Src: TCustomBitmap32; SrcX, SrcY: Integer;
  CombineOp: TDrawMode; CombineCallBack: TPixelCombineEvent);
var
  SrcP, DstP: PColor32;
  SP, DP: PColor32;
  MC: TColor32;
  W, I, DstY: Integer;
  BlendLine: TBlendLine;
  BlendLineEx: TBlendLineEx;
begin
  { Internal routine }
  W := DstRect.Right - DstRect.Left;
  SrcP := Src.PixelPtr[SrcX, SrcY];
  DstP := Dst.PixelPtr[DstRect.Left, DstRect.Top];

  case CombineOp of
    dmOpaque:
      begin
        for DstY := DstRect.Top to DstRect.Bottom - 1 do
        begin
          //Move(SrcP^, DstP^, W shl 2); // for FastCode
          MoveLongWord(SrcP^, DstP^, W);
          Inc(SrcP, Src.Width);
          Inc(DstP, Dst.Width);
        end;
      end;
    dmBlend:
      if Src.MasterAlpha >= 255 then
      begin
        BlendLine := BLEND_LINE[Src.CombineMode]^;
        for DstY := DstRect.Top to DstRect.Bottom - 1 do
        begin
          BlendLine(SrcP, DstP, W);
          Inc(SrcP, Src.Width);
          Inc(DstP, Dst.Width);
        end
      end
      else
      begin
        BlendLineEx := BLEND_LINE_EX[Src.CombineMode]^;
        for DstY := DstRect.Top to DstRect.Bottom - 1 do
        begin
          BlendLineEx(SrcP, DstP, W, Src.MasterAlpha);
          Inc(SrcP, Src.Width);
          Inc(DstP, Dst.Width);
        end
      end;
    dmTransparent:
      begin
        MC := Src.OuterColor;
        for DstY := DstRect.Top to DstRect.Bottom - 1 do
        begin
          SP := SrcP;
          DP := DstP;
          { TODO: Write an optimized routine for fast masked transfers. }
          for I := 0 to W - 1 do
          begin
            if MC <> SP^ then DP^ := SP^;
            Inc(SP); Inc(DP);
          end;
          Inc(SrcP, Src.Width);
          Inc(DstP, Dst.Width);
        end;
      end;
    else //  dmCustom:
      begin
        for DstY := DstRect.Top to DstRect.Bottom - 1 do
        begin
          SP := SrcP;
          DP := DstP;
          for I := 0 to W - 1 do
          begin
            CombineCallBack(SP^, DP^, Src.MasterAlpha);
            Inc(SP); Inc(DP);
          end;
          Inc(SrcP, Src.Width);
          Inc(DstP, Dst.Width);
        end;
      end;
    end;
end;


//------------------------------------------------------------------------------
//
//      BlockTransfer
//
//------------------------------------------------------------------------------
procedure BlockTransfer(
  Dst: TCustomBitmap32; DstX: Integer; DstY: Integer; DstClip: TRect;
  Src: TCustomBitmap32; SrcRect: TRect;
  CombineOp: TDrawMode; CombineCallBack: TPixelCombineEvent);
var
  SrcX, SrcY: Integer;
begin
  CheckBitmaps(Dst, Src);
  if Dst.Empty or Src.Empty or ((CombineOp = dmBlend) and (Src.MasterAlpha = 0)) then Exit;

  SrcX := SrcRect.Left;
  SrcY := SrcRect.Top;

  GR32.IntersectRect(DstClip, DstClip, Dst.BoundsRect);
  GR32.IntersectRect(SrcRect, SrcRect, Src.BoundsRect);

  GR32.OffsetRect(SrcRect, DstX - SrcX, DstY - SrcY);
  GR32.IntersectRect(SrcRect, DstClip, SrcRect);
  if GR32.IsRectEmpty(SrcRect) then
    exit;

  DstClip := SrcRect;
  GR32.OffsetRect(SrcRect, SrcX - DstX, SrcY - DstY);

  if not Dst.MeasuringMode then
  begin
    if (CombineOp = dmCustom) and not Assigned(CombineCallBack) then
      CombineOp := dmOpaque;

    BlendBlock(Dst, DstClip, Src, SrcRect.Left, SrcRect.Top, CombineOp, CombineCallBack);
  end;

  Dst.Changed(DstClip);
end;

//------------------------------------------------------------------------------

{$WARNINGS OFF}
procedure BlockTransferX(
  Dst: TCustomBitmap32; DstX, DstY: TFixed;
  Src: TCustomBitmap32; SrcRect: TRect;
  CombineOp: TDrawMode; CombineCallBack: TPixelCombineEvent = nil);
type
  TColor32Array = array [0..1] of TColor32;
  PColor32Array = ^TColor32Array;
var
  I, Index, SrcW, SrcRectW, SrcRectH, DstW, DstH: Integer;
  FracX, FracY: Integer;
  Buffer: array [0..1] of TArrayOfColor32;
  SrcP, Buf1, Buf2: PColor32Array;
  DstP: PColor32;
  C1, C2, C3, C4: TColor32;
  LW, RW, TW, BW, MA: Integer;
  DstBounds: TRect;

  BlendLineEx: TBlendLineEx;
  BlendMemEx: TBlendMemEx;
begin
  CheckBitmaps(Dst, Src);
  if Dst.Empty or Src.Empty or ((CombineOp = dmBlend) and (Src.MasterAlpha = 0)) then Exit;

  SrcRectW := SrcRect.Right - SrcRect.Left - 1;
  SrcRectH := SrcRect.Bottom - SrcRect.Top - 1;

  FracX := (DstX and $FFFF) shr 8;
  FracY := (DstY and $FFFF) shr 8;

  DstX := DstX div $10000;
  DstY := DstY div $10000;

  DstW := Dst.Width;
  DstH := Dst.Height;

  MA := Src.MasterAlpha;

  if (DstX >= DstW) or (DstY >= DstH) or (MA = 0) then Exit;

  if (DstX + SrcRectW <= 0) or (Dsty + SrcRectH <= 0) then Exit;

  if DstX < 0 then LW := $FF else LW := FracX xor $FF;
  if DstY < 0 then TW := $FF else TW := FracY xor $FF;
  if DstX + SrcRectW >= DstW then RW := $FF else RW := FracX;
  if DstY + SrcRectH >= DstH then BW := $FF else BW := FracY;

  DstBounds := Dst.BoundsRect;
  Dec(DstBounds.Right);
  Dec(DstBounds.Bottom);
  GR32.OffsetRect(DstBounds, SrcRect.Left - DstX, SrcRect.Top - DstY);
  GR32.IntersectRect(SrcRect, SrcRect, DstBounds);

  if GR32.IsRectEmpty(SrcRect) then Exit;

  SrcW := Src.Width;

  SrcRectW := SrcRect.Right - SrcRect.Left;
  SrcRectH := SrcRect.Bottom - SrcRect.Top;

  if DstX < 0 then DstX := 0;
  if DstY < 0 then DstY := 0;

  if not Dst.MeasuringMode then
  begin
    SetLength(Buffer[0], SrcRectW + 1);
    SetLength(Buffer[1], SrcRectW + 1);

    BlendLineEx := BLEND_LINE_EX[Src.CombineMode]^;
    BlendMemEx := BLEND_MEM_EX[Src.CombineMode]^;

    try
      SrcP := PColor32Array(Src.PixelPtr[SrcRect.Left, SrcRect.Top - 1]);
      DstP := Dst.PixelPtr[DstX, DstY];

      Buf1 := @Buffer[0][0];
      Buf2 := @Buffer[1][0];

      if SrcRect.Top > 0 then
      begin
        MoveLongWord(SrcP[0], Buf1[0], SrcRectW);
        CombineLine(@Buf1[1], @Buf1[0], SrcRectW, FracX);

        if SrcRect.Left > 0 then
          C2 := CombineReg(PColor32(NativeUInt(SrcP) - 4)^, SrcP[0], FracX xor $FF)
        else
          C2 := SrcP[0];

        if SrcRect.Right < SrcW then
          C4 := CombineReg(SrcP[SrcRectW - 1], SrcP[SrcRectW], FracX)
        else
          C4 := SrcP[SrcRectW - 1];
      end;

      Inc(PColor32(SrcP), SrcW);
      MoveLongWord(SrcP^, Buf2^, SrcRectW);
      CombineLine(@Buf2[1], @Buf2[0], SrcRectW, FracX xor $FF);

      if SrcRect.Left > 0 then
        C1 := CombineReg(PColor32(NativeUInt(SrcP) - 4)^, SrcP[0], FracX)
      else
        C1 := SrcP[0];

      if SrcRect.Right < SrcW then
        C3 := CombineReg(SrcP[SrcRectW - 1], SrcP[SrcRectW], FracX)
      else
        C3 := SrcP[SrcRectW - 1];

      if SrcRect.Top > 0 then
      begin
        BlendMemEx(CombineReg(C1, C2, FracY), DstP^, LW * TW * MA shr 16);
        CombineLine(@Buf2[0], @Buf1[0], SrcRectW, FracY xor $FF);
      end
      else
      begin
        BlendMemEx(C1, DstP^, LW * TW * MA shr 16);
        MoveLongWord(Buf2^, Buf1^, SrcRectW);
      end;

      Inc(DstP, 1);
      BlendLineEx(@Buf1[0], DstP, SrcRectW - 1, TW * MA shr 8);

      Inc(DstP, SrcRectW - 1);

      if SrcRect.Top > 0 then
        BlendMemEx(CombineReg(C3, C4, FracY), DstP^, RW * TW * MA shr 16)
      else
        BlendMemEx(C3, DstP^, RW * TW * MA shr 16);

      Inc(DstP, DstW - SrcRectW);

      Index := 1;
      for I := SrcRect.Top to SrcRect.Bottom - 2 do
      begin
        Buf1 := @Buffer[Index][0];
        Buf2 := @Buffer[Index xor 1][0];
        Inc(PColor32(SrcP), SrcW);

        MoveLongWord(SrcP[0], Buf2^, SrcRectW);

        // Horizontal translation
        CombineLine(@Buf2[1], @Buf2[0], SrcRectW, FracX xor $FF);

        if SrcRect.Left > 0 then
          C2 := CombineReg(PColor32(NativeUInt(SrcP) - 4)^, SrcP[0], FracX xor $FF)
        else
          C2 := SrcP[0];

        BlendMemEx(CombineReg(C1, C2, FracY), DstP^, LW * MA shr 8);
        Inc(DstP);
        C1 := C2;

        // Vertical translation
        CombineLine(@Buf2[0], @Buf1[0], SrcRectW, FracY xor $FF);

        // Blend horizontal line to Dst
        BlendLineEx(@Buf1[0], DstP, SrcRectW - 1, MA);
        Inc(DstP, SrcRectW - 1);

        if SrcRect.Right < SrcW then
          C4 := CombineReg(SrcP[SrcRectW - 1], SrcP[SrcRectW], FracX)
        else
          C4 := SrcP[SrcRectW - 1];

        BlendMemEx(CombineReg(C3, C4, FracY), DstP^, RW * MA shr 8);

        Inc(DstP, DstW - SrcRectW);
        C3 := C4;

        Index := Index xor 1;
      end;

      Buf1 := @Buffer[Index][0];
      Buf2 := @Buffer[Index xor 1][0];

      Inc(PColor32(SrcP), SrcW);

      if SrcRect.Bottom < Src.Height then
      begin
        MoveLongWord(SrcP[0], Buf2^, SrcRectW);
        CombineLine(@Buf2[1], @Buf2[0], SrcRectW, FracY xor $FF);
        CombineLine(@Buf2[0], @Buf1[0], SrcRectW, FracY xor $FF);
        if SrcRect.Left > 0 then
          C2 := CombineReg(PColor32(NativeUInt(SrcP) - 4)^, SrcP[0], FracX xor $FF)
        else
          C2 := SrcP[0];
        BlendMemEx(CombineReg(C1, C2, FracY), DstP^, LW * BW * MA shr 16)
      end
      else
        BlendMemEx(C1, DstP^, LW * BW * MA shr 16);

      Inc(DstP);
      BlendLineEx(@Buf1[0], DstP, SrcRectW - 1, BW * MA shr 8);
      Inc(DstP, SrcRectW - 1);

      if SrcRect.Bottom < Src.Height then
      begin
        if SrcRect.Right < SrcW then
          C4 := CombineReg(SrcP[SrcRectW - 1], SrcP[SrcRectW], FracX)
        else
          C4 := SrcP[SrcRectW - 1];
        BlendMemEx(CombineReg(C3, C4, FracY), DstP^, RW * BW * MA shr 16);
      end
      else
        BlendMemEx(C3, DstP^, RW * BW * MA shr 16);

    finally
      Buffer[0] := nil;
      Buffer[1] := nil;
    end;
  end;

  Dst.Changed(MakeRect(DstX, DstY, DstX + SrcRectW + 1, DstY + SrcRectH + 1));
end;
{$WARNINGS ON}


//------------------------------------------------------------------------------
//
//      BlendTransfer
//
//------------------------------------------------------------------------------
procedure BlendTransfer(
  Dst: TCustomBitmap32; DstX, DstY: Integer; DstClip: TRect;
  SrcF: TCustomBitmap32; SrcRectF: TRect;
  SrcB: TCustomBitmap32; SrcRectB: TRect;
  BlendCallback: TBlendReg);
var
  I, J, SrcFX, SrcFY, SrcBX, SrcBY: Integer;
  PSrcF, PSrcB, PDst: PColor32Array;
begin
  if not Assigned(Dst) then raise EBitmapException.Create(SDstNil);
  if not Assigned(SrcF) then raise EBitmapException.Create(SSrcNil);
  if not Assigned(SrcB) then raise EBitmapException.Create(SSrcNil);

  if Dst.Empty or SrcF.Empty or SrcB.Empty or not Assigned(BlendCallback) then Exit;

  if not Dst.MeasuringMode then
  begin
    SrcFX := SrcRectF.Left - DstX;
    SrcFY := SrcRectF.Top - DstY;
    SrcBX := SrcRectB.Left - DstX;
    SrcBY := SrcRectB.Top - DstY;

    GR32.IntersectRect(DstClip, DstClip, Dst.BoundsRect);
    GR32.IntersectRect(SrcRectF, SrcRectF, SrcF.BoundsRect);
    GR32.IntersectRect(SrcRectB, SrcRectB, SrcB.BoundsRect);

    GR32.OffsetRect(SrcRectF, -SrcFX, -SrcFY);
    GR32.OffsetRect(SrcRectB, -SrcBX, -SrcBY);

    GR32.IntersectRect(DstClip, DstClip, SrcRectF);
    GR32.IntersectRect(DstClip, DstClip, SrcRectB);

    if not GR32.IsRectEmpty(DstClip) then
      for I := DstClip.Top to DstClip.Bottom - 1 do
      begin
        PSrcF := PColor32Array(SrcF.PixelPtr[SrcFX, SrcFY + I]);
        PSrcB := PColor32Array(SrcB.PixelPtr[SrcBX, SrcBY + I]);
        PDst := Dst.ScanLine[I];
        for J := DstClip.Left to DstClip.Right - 1 do
          PDst[J] := BlendCallback(PSrcF[J], PSrcB[J]);
      end;
  end;
  Dst.Changed(DstClip);
end;

//------------------------------------------------------------------------------

procedure BlendTransfer(
  Dst: TCustomBitmap32; DstX, DstY: Integer; DstClip: TRect;
  SrcF: TCustomBitmap32; SrcRectF: TRect;
  SrcB: TCustomBitmap32; SrcRectB: TRect;
  BlendCallback: TBlendRegEx; MasterAlpha: Integer);
var
  I, J, SrcFX, SrcFY, SrcBX, SrcBY: Integer;
  PSrcF, PSrcB, PDst: PColor32Array;
begin
  if not Assigned(Dst) then raise EBitmapException.Create(SDstNil);
  if not Assigned(SrcF) then raise EBitmapException.Create(SSrcNil);
  if not Assigned(SrcB) then raise EBitmapException.Create(SSrcNil);

  if Dst.Empty or SrcF.Empty or SrcB.Empty or not Assigned(BlendCallback) then Exit;

  if not Dst.MeasuringMode then
  begin
    SrcFX := SrcRectF.Left - DstX;
    SrcFY := SrcRectF.Top - DstY;
    SrcBX := SrcRectB.Left - DstX;
    SrcBY := SrcRectB.Top - DstY;

    GR32.IntersectRect(DstClip, DstClip, Dst.BoundsRect);
    GR32.IntersectRect(SrcRectF, SrcRectF, SrcF.BoundsRect);
    GR32.IntersectRect(SrcRectB, SrcRectB, SrcB.BoundsRect);

    GR32.OffsetRect(SrcRectF, -SrcFX, -SrcFY);
    GR32.OffsetRect(SrcRectB, -SrcBX, -SrcBY);

    GR32.IntersectRect(DstClip, DstClip, SrcRectF);
    GR32.IntersectRect(DstClip, DstClip, SrcRectB);

    if not GR32.IsRectEmpty(DstClip) then
      for I := DstClip.Top to DstClip.Bottom - 1 do
      begin
        PSrcF := PColor32Array(SrcF.PixelPtr[SrcFX, SrcFY + I]);
        PSrcB := PColor32Array(SrcB.PixelPtr[SrcBX, SrcBY + I]);
        PDst := Dst.ScanLine[I];
        for J := DstClip.Left to DstClip.Right - 1 do
          PDst[J] := BlendCallback(PSrcF[J], PSrcB[J], MasterAlpha);
      end;
  end;
  Dst.Changed(DstClip);
end;


//------------------------------------------------------------------------------
//
//      StretchNearest
//
//------------------------------------------------------------------------------
// Used by TNearestResampler.Resample
//------------------------------------------------------------------------------
procedure StretchNearest(
  Dst: TCustomBitmap32; DstRect, DstClip: TRect;
  Src: TCustomBitmap32; SrcRect: TRect;
  CombineOp: TDrawMode; CombineCallBack: TPixelCombineEvent);
var
  R: TRect;
  SrcW, SrcH, DstW, DstH, DstClipW, DstClipH: Integer;
  SrcY, OldSrcY: Integer;
  I, J: Integer;
  MapHorz: PIntegerArray;
  SrcLine, DstLine: PColor32Array;
  Buffer: TArrayOfColor32;
  Scale: TFloat;
  BlendLine: TBlendLine;
  BlendLineEx: TBlendLineEx;
  DstLinePtr, MapPtr: PColor32;
begin
  GR32.IntersectRect(DstClip, DstClip, MakeRect(0, 0, Dst.Width, Dst.Height));
  GR32.IntersectRect(DstClip, DstClip, DstRect);
  if GR32.IsRectEmpty(DstClip) then Exit;
  GR32.IntersectRect(R, DstClip, DstRect);
  if GR32.IsRectEmpty(R) then Exit;
  if (SrcRect.Left < 0) or (SrcRect.Top < 0) or (SrcRect.Right > Src.Width) or
    (SrcRect.Bottom > Src.Height) then
    raise Exception.Create(RCStrInvalidSrcRect);

  SrcW := SrcRect.Right - SrcRect.Left;
  SrcH := SrcRect.Bottom - SrcRect.Top;
  DstW := DstRect.Right - DstRect.Left;
  DstH := DstRect.Bottom - DstRect.Top;
  DstClipW := DstClip.Right - DstClip.Left;
  DstClipH := DstClip.Bottom - DstClip.Top;

  if (SrcW = DstW) and (SrcH = DstH) then
  begin
    { Copy without resampling }
    BlendBlock(Dst, DstClip, Src, SrcRect.Left + DstClip.Left - DstRect.Left,
      SrcRect.Top + DstClip.Top - DstRect.Top, CombineOp, CombineCallBack);
  end
  else
  begin
    GetMem(MapHorz, DstClipW * SizeOf(Integer));
    try
      if DstW > 1 then
      begin
        if FullEdge then
        begin
          Scale := SrcW / DstW;
          for I := 0 to DstClipW - 1 do
            MapHorz^[I] := Trunc(SrcRect.Left + (I + DstClip.Left - DstRect.Left) * Scale);
        end
        else
        begin
          Scale := (SrcW - 1) / (DstW - 1);
          for I := 0 to DstClipW - 1 do
            MapHorz^[I] := Round(SrcRect.Left + (I + DstClip.Left - DstRect.Left) * Scale);
        end;
        
        Assert(MapHorz^[0] >= SrcRect.Left);
        Assert(MapHorz^[DstClipW - 1] < SrcRect.Right);
      end
      else
        MapHorz^[0] := (SrcRect.Left + SrcRect.Right - 1) div 2;

      if DstH <= 1 then Scale := 0
      else if FullEdge then Scale := SrcH / DstH
      else Scale := (SrcH - 1) / (DstH - 1);

      if CombineOp = dmOpaque then
      begin
        DstLine := PColor32Array(Dst.PixelPtr[DstClip.Left, DstClip.Top]);
        OldSrcY := -1;
        
        for J := 0 to DstClipH - 1 do
        begin
          if DstH <= 1 then
            SrcY := (SrcRect.Top + SrcRect.Bottom - 1) div 2
          else if FullEdge then
            SrcY := Trunc(SrcRect.Top + (J + DstClip.Top - DstRect.Top) * Scale)
          else
            SrcY := Round(SrcRect.Top + (J + DstClip.Top - DstRect.Top) * Scale);
            
          if SrcY <> OldSrcY then
          begin
            SrcLine := Src.ScanLine[SrcY];
            DstLinePtr := @DstLine[0];
            MapPtr := @MapHorz^[0];
            for I := 0 to DstClipW - 1 do
            begin
              DstLinePtr^ := SrcLine[MapPtr^];
              Inc(DstLinePtr);
              Inc(MapPtr);
            end;
            OldSrcY := SrcY;
          end
          else
            MoveLongWord(DstLine[-Dst.Width], DstLine[0], DstClipW);
          Inc(DstLine, Dst.Width);
        end;
      end
      else
      begin
        SetLength(Buffer, DstClipW);
        DstLine := PColor32Array(Dst.PixelPtr[DstClip.Left, DstClip.Top]);
        OldSrcY := -1;

        if Src.MasterAlpha >= 255 then
        begin
          BlendLine := BLEND_LINE[Src.CombineMode]^;
          BlendLineEx := nil; // stop compiler warnings...
        end
        else
        begin
          BlendLineEx := BLEND_LINE_EX[Src.CombineMode]^;
          BlendLine := nil; // stop compiler warnings...
        end;

        for J := 0 to DstClipH - 1 do
        begin
          if DstH > 1 then
          begin
            if FullEdge then
              SrcY := Trunc(SrcRect.Top + (J + DstClip.Top - DstRect.Top) * Scale)
            else
              SrcY := Round(SrcRect.Top + (J + DstClip.Top - DstRect.Top) * Scale);
          end
          else
            SrcY := (SrcRect.Top + SrcRect.Bottom - 1) div 2;
            
          if SrcY <> OldSrcY then
          begin
            SrcLine := Src.ScanLine[SrcY];
            DstLinePtr := @Buffer[0];
            MapPtr := @MapHorz^[0];
            for I := 0 to DstClipW - 1 do
            begin
              DstLinePtr^ := SrcLine[MapPtr^];
              Inc(DstLinePtr);
              Inc(MapPtr);
            end;
            OldSrcY := SrcY;
          end;

          case CombineOp of
            dmBlend:
              if Src.MasterAlpha >= 255 then
                BlendLine(@Buffer[0], @DstLine[0], DstClipW)
              else
                BlendLineEx(@Buffer[0], @DstLine[0], DstClipW, Src.MasterAlpha);
            dmTransparent:
              for I := 0 to DstClipW - 1 do
                if Buffer[I] <> Src.OuterColor then DstLine[I] := Buffer[I];
            dmCustom:
              for I := 0 to DstClipW - 1 do
                CombineCallBack(Buffer[I], DstLine[I], Src.MasterAlpha);
          end;

          Inc(DstLine, Dst.Width);
        end;
      end;
    finally
      FreeMem(MapHorz);
    end;
  end;
end;


//------------------------------------------------------------------------------
//
//      StretchNearest
//
//------------------------------------------------------------------------------
// Used by TDraftResampler.Resample (via DraftResample) and TLinearResampler.Resample
//------------------------------------------------------------------------------
procedure StretchHorzStretchVertLinear(
  Dst: TCustomBitmap32; DstRect, DstClip: TRect;
  Src: TCustomBitmap32; SrcRect: TRect;
  CombineOp: TDrawMode; CombineCallBack: TPixelCombineEvent);
//Assure DstRect is >= SrcRect, otherwise quality loss will occur
var
  SrcW, SrcH, DstW, DstH, DstClipW, DstClipH: Integer;
  MapHorz, MapVert: array of TPointRec;
  t2, Scale: TFloat;
  SrcLine, DstLine: PColor32Array;
  SrcIndex: Integer;
  SrcPtr1, SrcPtr2: PColor32;
  I, J: Integer;
  WY: Cardinal;
  C: TColor32;
  BlendMemEx: TBlendMemEx;
begin
  SrcW := SrcRect.Right - SrcRect.Left;
  SrcH := SrcRect.Bottom - SrcRect.Top;
  DstW := DstRect.Right - DstRect.Left;
  DstH := DstRect.Bottom - DstRect.Top;
  DstClipW := DstClip.Right - DstClip.Left;
  DstClipH := DstClip.Bottom - DstClip.Top;

  SetLength(MapHorz, DstClipW);
  if FullEdge then Scale := SrcW / DstW
  else Scale := (SrcW - 1) / (DstW - 1);
  for I := 0 to DstClipW - 1 do
  begin
    if FullEdge then t2 := SrcRect.Left - 0.5 + (I + DstClip.Left - DstRect.Left + 0.5) * Scale
    else t2 := SrcRect.Left + (I + DstClip.Left - DstRect.Left) * Scale;
    if t2 < 0 then t2 := 0
    else if t2 > Src.Width - 1 then t2 := Src.Width - 1;
    MapHorz[I].Pos := Floor(t2);
    MapHorz[I].Weight := 256 - Round(Frac(t2) * 256);
    //Pre-pack weights to reduce MMX Reg. setups per pixel:
    //MapHorz[I].Weight:= MapHorz[I].Weight shl 16 + MapHorz[I].Weight;
  end;
  I := DstClipW - 1;
  while MapHorz[I].Pos = SrcRect.Right - 1 do
  begin
    Dec(MapHorz[I].Pos);
    MapHorz[I].Weight := 0;
    Dec(I);
  end;

  SetLength(MapVert, DstClipH);
  if FullEdge then Scale := SrcH / DstH
  else Scale := (SrcH - 1) / (DstH - 1);
  for I := 0 to DstClipH - 1 do
  begin
    if FullEdge then t2 := SrcRect.Top - 0.5 + (I + DstClip.Top - DstRect.Top + 0.5) * Scale
    else t2 := SrcRect.Top + (I + DstClip.Top - DstRect.Top) * Scale;
    if t2 < 0 then t2 := 0
    else if t2 > Src.Height - 1 then t2 := Src.Height - 1;
    MapVert[I].Pos := Floor(t2);
    MapVert[I].Weight := 256 - Round(Frac(t2) * 256);
    //Pre-pack weights to reduce MMX Reg. setups per pixel:
    //MapVert[I].Weight := MapVert[I].Weight shl 16 + MapVert[I].Weight;
  end;
  I := DstClipH - 1;
  while MapVert[I].Pos = SrcRect.Bottom - 1 do
  begin
    Dec(MapVert[I].Pos);
    MapVert[I].Weight := 0;
    Dec(I);
  end;

  DstLine := PColor32Array(Dst.PixelPtr[DstClip.Left, DstClip.Top]);
  SrcW := Src.Width;
  DstW := Dst.Width;
  case CombineOp of
    dmOpaque:
      for J := 0 to DstClipH - 1 do
      begin
        SrcLine := Src.ScanLine[MapVert[J].Pos];
        WY := MapVert[J].Weight;

        SrcIndex := MapHorz[0].Pos;
        SrcPtr1 := @SrcLine[SrcIndex];
        SrcPtr2 := @SrcLine[SrcIndex + SrcW];
        for I := 0 to DstClipW - 1 do
        begin
          if SrcIndex <> MapHorz[I].Pos then
          begin
            SrcIndex := MapHorz[I].Pos;
            SrcPtr1 := @SrcLine[SrcIndex];
            SrcPtr2 := @SrcLine[SrcIndex + SrcW];
          end;
          DstLine[I] := Interpolator(MapHorz[I].Weight, WY, SrcPtr1, SrcPtr2);
        end;
        Inc(DstLine, DstW);
      end;

    dmBlend:
      begin
        BlendMemEx := BLEND_MEM_EX[Src.CombineMode]^;
        for J := 0 to DstClipH - 1 do
        begin
          SrcLine := Src.ScanLine[MapVert[J].Pos];
          WY := MapVert[J].Weight;
          SrcIndex := MapHorz[0].Pos;
          SrcPtr1 := @SrcLine[SrcIndex];
          SrcPtr2 := @SrcLine[SrcIndex + SrcW];
          for I := 0 to DstClipW - 1 do
          begin
            if SrcIndex <> MapHorz[I].Pos then
            begin
              SrcIndex := MapHorz[I].Pos;
              SrcPtr1 := @SrcLine[SrcIndex];
              SrcPtr2 := @SrcLine[SrcIndex + SrcW];
            end;
            C := Interpolator(MapHorz[I].Weight, WY, SrcPtr1, SrcPtr2);
            BlendMemEx(C, DstLine[I], Src.MasterAlpha)
          end;
          Inc(DstLine, Dst.Width);
        end
      end;

    dmTransparent:
      begin
        for J := 0 to DstClipH - 1 do
        begin
          SrcLine := Src.ScanLine[MapVert[J].Pos];
          WY := MapVert[J].Weight;
          SrcIndex := MapHorz[0].Pos;
          SrcPtr1 := @SrcLine[SrcIndex];
          SrcPtr2 := @SrcLine[SrcIndex + SrcW];
          for I := 0 to DstClipW - 1 do
          begin
            if SrcIndex <> MapHorz[I].Pos then
            begin
              SrcIndex := MapHorz[I].Pos;
              SrcPtr1 := @SrcLine[SrcIndex];
              SrcPtr2 := @SrcLine[SrcIndex + SrcW];
            end;
            C := Interpolator(MapHorz[I].Weight, WY, SrcPtr1, SrcPtr2);
            if C <> Src.OuterColor then DstLine[I] := C;
          end;
          Inc(DstLine, Dst.Width);
        end
      end;
  else // cmCustom
    for J := 0 to DstClipH - 1 do
    begin
      SrcLine := Src.ScanLine[MapVert[J].Pos];
      WY := MapVert[J].Weight;
      SrcIndex := MapHorz[0].Pos;    
      SrcPtr1 := @SrcLine[SrcIndex];    
      SrcPtr2 := @SrcLine[SrcIndex + SrcW];    
      for I := 0 to DstClipW - 1 do    
      begin    
        if SrcIndex <> MapHorz[I].Pos then    
        begin    
          SrcIndex := MapHorz[I].Pos;    
          SrcPtr1 := @SrcLine[SrcIndex];    
          SrcPtr2 := @SrcLine[SrcIndex + SrcW];
        end;
        C := Interpolator(MapHorz[I].Weight, WY, SrcPtr1, SrcPtr2);
        CombineCallBack(C, DstLine[I], Src.MasterAlpha);
      end;
      Inc(DstLine, Dst.Width);
    end;
  end;
end;


//------------------------------------------------------------------------------
//
//      Resample
//
//------------------------------------------------------------------------------
// Primarily used by TKernelResampler.Resample
//------------------------------------------------------------------------------
// Precision of TMappingTable[][].Weight.
// Totals Cb,Cg,Cr,Ca in Resample need to be unscaled by (1 shl MappingTablePrecicionShift2).
const
  // Weight precision
{$ifdef PREMULTIPLY}
  MappingTablePrecicionShift = 8; // Fixed precision [24:8]
{$else PREMULTIPLY}
  MappingTablePrecicionShift = 11; // Fixed precision [21:11]
{$endif PREMULTIPLY}
  MappingTablePrecicionShift2 = 2 * MappingTablePrecicionShift;
  MappingTablePrecicion = 1 shl MappingTablePrecicionShift;
  MappingTablePrecicion2 = 1 shl MappingTablePrecicionShift2;
  MappingTablePrecicionRound = (1 shl MappingTablePrecicionShift2) div 2 - 1;
  MappingTablePrecicionMax2 = 255 shl MappingTablePrecicionShift2;

{$ifdef PREMULTIPLY}
const
  // Premultiplication
  // Max error across all value[0..255]/alpha[1..255] combinations:
  //   Shift=1: +/-1
  //   Shift=2: +/-3
  //   Shift=3: +/-7     in other words: error = +/- 2^(shift-1)
  //   Shift=4: +/-15
  //   Shift=5: +/-31
  MappingTablePremultPrecicionShift = 2; // [0..7]
  MappingTablePremultPrecicion = 1 shl MappingTablePremultPrecicionShift;
{$endif PREMULTIPLY}

//------------------------------------------------------------------------------
// BuildMappingTable
//------------------------------------------------------------------------------
function BuildMappingTable(DstLo, DstHi: Integer; ClipLo, ClipHi: Integer;
  SrcLo, SrcHi: Integer; Kernel: TCustomKernel): TMappingTable;
var
  SrcWidth, DstWidth, ClipWidth: Integer;
  Filter: TFilterMethod;
  FilterWidth: TFloat;
  Scale, InvScale: TFloat;
  Center: TFloat;
  Count: Integer;
  Left, Right: Integer;
  I, J, K: Integer;
  Weight: Integer;
  x0, x1, x2, x3: TFloat;
begin
  SrcWidth := SrcHi - SrcLo;
  DstWidth := DstHi - DstLo;
  ClipWidth := ClipHi - ClipLo;

  if SrcWidth = 0 then
  begin
    Result := nil;
    Exit;
  end;

  if SrcWidth = 1 then
  begin
    SetLength(Result, ClipWidth);
    for I := 0 to ClipWidth - 1 do
    begin
      SetLength(Result[I], 1);
      Result[I][0].Pos := SrcLo;
      Result[I][0].Weight := MappingTablePrecicion; // Weight=1
    end;
    Exit;
  end;

  SetLength(Result, ClipWidth);
  if ClipWidth = 0 then
    Exit;

  if FullEdge then
    Scale := DstWidth / SrcWidth
  else
    Scale := (DstWidth - 1) / (SrcWidth - 1);

  Filter := Kernel.Filter;
  FilterWidth := Kernel.GetWidth;
  K := 0;

  if Scale = 0 then
  begin
    Assert(Length(Result) = 1);
    SetLength(Result[0], 1);
    Result[0][0].Pos := (SrcLo + SrcHi) div 2;
    Result[0][0].Weight := MappingTablePrecicion; // Weight=1
  end else
  if Scale < 1 then
  begin
    InvScale := Scale;
    Scale := 1 / Scale;
    FilterWidth := FilterWidth * Scale;
    for I := 0 to ClipWidth - 1 do
    begin
      if FullEdge then
        Center := SrcLo - 0.5 + (I - DstLo + ClipLo + 0.5) * Scale
      else
        Center := SrcLo + (I - DstLo + ClipLo) * Scale;

      Left := Floor(Center - FilterWidth);
      Right := Ceil(Center + FilterWidth);

      Count := -MappingTablePrecicion;
      for J := Left to Right do
      begin
        //
        // Compute the intergral for the convolution with the filter using the midpoint-rule:
        //
        // Assume that f(x) is continuous on [a, b], n is a positive integer and
        //
        //         b - a
        //   ∆x = -------
        //           n
        //
        // If [a,b] is divided into n subintervals, each of length ∆x, and m{i} is the midpoint
        // of the i'th subinterval, set
        //
        //   M{n} = ∑ f(m{i}) ∆x
        //
        // then
        //
        //   M{n} ≈ ∫ f(x)dx
        //
        // In other words, the integral from x1 to x2 of f(x) dx is approximately:
        //
        //   f((x1+x2)/2)*(x2-x1). ﻿
        //
        x0 := J - Center;
        x1 := Max(x0 - 0.5, -FilterWidth);
        x2 := Min(x0 + 0.5, FilterWidth);
        x3 := (x2 + x1) * 0.5; // Center of [x1, x2]

        Weight := Round(MappingTablePrecicion * Filter(x3 * InvScale) * (x2 - x1) * InvScale);

        if Weight <> 0 then
        begin
          Inc(Count, Weight);
          K := Length(Result[I]);
          SetLength(Result[I], K + 1);
          Result[I][K].Pos := Constrain(J, SrcLo, SrcHi - 1);
          Result[I][K].Weight := Weight;
        end;
      end;

      if Length(Result[I]) = 0 then
      begin
        SetLength(Result[I], 1);
        Result[I][0].Pos := Floor(Center);
        Result[I][0].Weight := MappingTablePrecicion;
      end else
      if Count <> 0 then
        Dec(Result[I][K div 2].Weight, Count);
    end;
  end
  else // scale > 1
  begin
    Scale := 1 / Scale;
    for I := 0 to ClipWidth - 1 do
    begin
      if FullEdge then
        Center := SrcLo - 0.5 + (I - DstLo + ClipLo + 0.5) * Scale
      else
        Center := SrcLo + (I - DstLo + ClipLo) * Scale;

      Left := Floor(Center - FilterWidth);
      Right := Ceil(Center + FilterWidth);

      Count := -MappingTablePrecicion;
      for J := Left to Right do
      begin
        x0 := J - Center;
        x1 := Max(x0 - 0.5, -FilterWidth);
        x2 := Min(x0 + 0.5, FilterWidth);
        x3 := (x1 + x2) * 0.5;

        Weight := Round(MappingTablePrecicion * Filter(x3) * (x2 - x1));

        if Weight <> 0 then
        begin
          Inc(Count, Weight);
          K := Length(Result[I]);
          SetLength(Result[I], K + 1);
          Result[I][K].Pos := Constrain(J, SrcLo, SrcHi - 1);
          Result[I][K].Weight := Weight;
        end;
      end;
      if Count <> 0 then
        Dec(Result[I][K div 2].Weight, Count);
    end;
  end;
end;

//------------------------------------------------------------------------------
// Premultiply
//------------------------------------------------------------------------------
{$ifdef PREMULTIPLY}
function Premultiply(Value, Alpha: integer): integer; {$IFDEF USEINLINING} inline; {$ENDIF}
begin
  // Instead of performing a full traditional premultiplication:
  //
  //   RGBp = RGB * Alpha / 255
  //
  // we try to lessen the rounding error, which is normally
  // introduced when this is done in integer precision, by
  // using a smaller divisor. Additionally we use a power of 2
  // divisor so the division can be done with a simple shift:
  //
  //   RGBp = RGB * Alpha >> X
  //
  // We need to use "div" for division instead of a direct "shr" as
  // "shr" performs a logical shift and not an arithmetic shift.
  // The compiler will optimize a "div" with a power of 2 constant
  // divisor to an arithmetic shift, so it's a very cheap operation.
  Result := (Value * Alpha) div MappingTablePremultPrecicion;
end;

//------------------------------------------------------------------------------
// Unpremultiply
//------------------------------------------------------------------------------
function Unpremultiply(Value, Alpha: integer): integer; {$IFDEF USEINLINING} inline; {$ENDIF}
begin
  // It would be best if we could do the multiplication before the division
  // but unfortunately that overflows the fixed precision.
  Result := (Value div Alpha) * MappingTablePremultPrecicion;
end;
{$endif PREMULTIPLY}

//------------------------------------------------------------------------------
// Resample
//------------------------------------------------------------------------------
procedure Resample(
  Dst: TCustomBitmap32; DstRect: TRect; DstClip: TRect;
  Src: TCustomBitmap32; SrcRect: TRect;
  Kernel: TCustomKernel;
  CombineOp: TDrawMode; CombineCallBack: TPixelCombineEvent);
var
  DstClipW: Integer;
  MapX, MapY: TMappingTable;
  I, J, X, Y: Integer;
  MapXLoPos, MapXHiPos: Integer;
  HorzBuffer: array of TBufferEntry;
  ClusterX, ClusterY: TCluster;
  Cb, Cg, Cr, Ca: Integer;
  C: TColor32Entry;
  ClusterWeight: Integer;
  DstLine: PColor32Array;
  RangeCheck: Boolean;
  BlendMemEx: TBlendMemEx;
  SourceColor: PColor32Entry;
  BufferEntry: PBufferEntry;
{$ifdef PREMULTIPLY}
  Alpha: integer;
  DoPremultiply: boolean;
{$endif PREMULTIPLY}
begin
  if (CombineOp = dmCustom) and not Assigned(CombineCallBack) then
    CombineOp := dmOpaque;

  { check source and destination }
  if (CombineOp = dmBlend) and (Src.MasterAlpha = 0) then
    Exit;

  BlendMemEx := BLEND_MEM_EX[Src.CombineMode]^; // store in local variable

  DstClipW := DstClip.Right - DstClip.Left;

  // Mapping tables
  MapX := BuildMappingTable(DstRect.Left, DstRect.Right, DstClip.Left, DstClip.Right, SrcRect.Left, SrcRect.Right, Kernel);
  MapY := BuildMappingTable(DstRect.Top, DstRect.Bottom, DstClip.Top, DstClip.Bottom, SrcRect.Top, SrcRect.Bottom, Kernel);
  if (MapX = nil) or (MapY = nil) then
    Exit;

{$ifdef PREMULTIPLY}
  // Scan bitmap for alpha
  DoPremultiply := False;
  SourceColor := PColor32Entry(Src.Bits);
  I := Src.Height*Src.Width;
  while (I > 0) do
  begin
    if (SourceColor.A <> 255) and (SourceColor.A <> 0) then
    begin
      // We only need to do alpha-premultiplication if Alpha exist in range [1..254]
      DoPremultiply := True;
      break;
    end;
    Inc(SourceColor);
    Dec(I);
  end;
{$endif PREMULTIPLY}

  ClusterX := nil;
  ClusterY := nil;

{$ifdef PREMULTIPLY}
  // If we're doing premultiplication then we always need to clamp the unpremultiplied
  // values. Why? Well, premult/unpremult normally goes like this:
  //
  //   RGBp = RGB * Alpha / 255
  //   RGB = RGBp * 255 / Alpha
  //
  // or in this particular case:
  //
  //   RGBp = RGB * Alpha / 255
  //   RGB = ∑RGBp * 255 / ∑Alpha
  //
  // Now in case the rounding of the RGB or Alpha values leads to (∑RGBp > RGBp) or
  // (Alpha > ∑Alpha) then we will get RGB values out of bounds (i.e. > 255).

  RangeCheck := DoPremultiply or Kernel.RangeCheck;
{$else PREMULTIPLY}
  RangeCheck := Kernel.RangeCheck;
{$endif PREMULTIPLY}

  MapXLoPos := MapX[0][0].Pos;
  MapXHiPos := MapX[DstClipW - 1][High(MapX[DstClipW - 1])].Pos;
  SetLength(HorzBuffer, MapXHiPos - MapXLoPos + 1);

  { transfer pixels }
  for J := DstClip.Top to DstClip.Bottom - 1 do
  begin
    ClusterY := MapY[J - DstClip.Top];
    ClusterWeight := ClusterY[0].Weight;

    SourceColor := @Src.Bits[ClusterY[0].Pos * Src.Width + MapXLoPos];
    BufferEntry := @HorzBuffer[0];

    X := MapXHiPos - MapXLoPos;
    while (X >= 0) do // for X := MapXLoPos to MapXHiPos do
    begin
{$ifdef PREMULTIPLY}
      // Alpha=0 should not contribute to sample.
      Alpha := SourceColor.A;
      if (Alpha <> 0) then
      begin
        Alpha := Alpha * ClusterWeight;
        if (DoPremultiply) then
        begin
          // Sample premultiplied values
          // RGB is multiplied with Alpha during premultiplication so instead of
          //   BufferEntry.RGB := Premultiply(SourceColor.RGB * ClusterWeight, Alpha);
          // we're doing
          //   Alpha := Alpha * ClusterWeight;
          //   BufferEntry.RGB := Premultiply(SourceColor.RGB, Alpha);
          // and saving 3 multiplications.
          BufferEntry.B := Premultiply(SourceColor.B, Alpha);
          BufferEntry.G := Premultiply(SourceColor.G, Alpha);
          BufferEntry.R := Premultiply(SourceColor.R, Alpha);
        end else
        begin
          BufferEntry.B := SourceColor.B * ClusterWeight;
          BufferEntry.G := SourceColor.G * ClusterWeight;
          BufferEntry.R := SourceColor.R * ClusterWeight;
        end;
        BufferEntry.A := Alpha;
      end else
        BufferEntry^ := Default(TBufferEntry);
{$else PREMULTIPLY}
      // Alpha=0 should not contribute to sample.
      if (SourceColor.A <> 0) then
      begin
        BufferEntry.B := SourceColor.B * ClusterWeight;
        BufferEntry.G := SourceColor.G * ClusterWeight;
        BufferEntry.R := SourceColor.R * ClusterWeight;
        BufferEntry.A := SourceColor.A * ClusterWeight;
      end else
        BufferEntry^ := Default(TBufferEntry);
{$endif PREMULTIPLY}
      Inc(SourceColor);
      Inc(BufferEntry);
      Dec(X);
    end;

    Y := Length(ClusterY) - 1;
    while (Y > 0) do // for Y := 1 to Length(ClusterY) - 1 do
    begin
      ClusterWeight := ClusterY[Y].Weight;

      SourceColor := @Src.Bits[ClusterY[Y].Pos * Src.Width + MapXLoPos];
      BufferEntry := @HorzBuffer[0];

      X := MapXHiPos - MapXLoPos;
      while (X >= 0) do // for X := MapXLoPos to MapXHiPos do
      begin
{$ifdef PREMULTIPLY}
        // Alpha=0 should not contribute to sample.
        Alpha := SourceColor.A;
        if (Alpha <> 0) then
        begin
          Alpha := Alpha * ClusterWeight;
          if (DoPremultiply) then
          begin
            // Sample premultiplied values
            Inc(BufferEntry.B, Premultiply(SourceColor.B, Alpha));
            Inc(BufferEntry.G, Premultiply(SourceColor.G, Alpha));
            Inc(BufferEntry.R, Premultiply(SourceColor.R, Alpha));
          end else
          begin
            Inc(BufferEntry.B, SourceColor.B * ClusterWeight);
            Inc(BufferEntry.G, SourceColor.G * ClusterWeight);
            Inc(BufferEntry.R, SourceColor.R * ClusterWeight);
          end;
          Inc(BufferEntry.A, Alpha);
        end;
{$else PREMULTIPLY}
        // Alpha=0 should not contribute to sample.
        if (SourceColor.A <> 0) then
        begin
          Inc(BufferEntry.B, SourceColor.B * ClusterWeight);
          Inc(BufferEntry.G, SourceColor.G * ClusterWeight);
          Inc(BufferEntry.R, SourceColor.R * ClusterWeight);
          Inc(BufferEntry.A, SourceColor.A * ClusterWeight);
        end;
{$endif PREMULTIPLY}
        Inc(SourceColor);
        Inc(BufferEntry);
        Dec(X);
      end;
      Dec(Y);
    end;

    DstLine := Dst.ScanLine[J];
    for I := DstClip.Left to DstClip.Right - 1 do
    begin
      Cb := 0; Cg := Cb; Cr := Cb; Ca := Cb;

      ClusterX := MapX[I - DstClip.Left];

      X := Length(ClusterX) - 1;
      while (X >= 0) do // for X := 0 to Length(ClusterX) - 1 do
      begin
        with HorzBuffer[ClusterX[X].Pos - MapXLoPos] do
          if (A <> 0) then // If Alpha=0 then RGB=0
          begin
            ClusterWeight := ClusterX[X].Weight;
            Inc(Cb, B * ClusterWeight); // Note: Fixed precision multiplication done here
            Inc(Cg, G * ClusterWeight);
            Inc(Cr, R * ClusterWeight);
            Inc(Ca, A * ClusterWeight);
          end;
        Dec(X);
      end;

      // Unpremultiply, unscale and round
      if RangeCheck then
      begin
{$ifdef PREMULTIPLY}
        Alpha:= (Clamp(Ca, 0, MappingTablePrecicionMax2) + MappingTablePrecicionRound) shr MappingTablePrecicionShift2;
        if (Alpha <> 0) then
        begin
          if (DoPremultiply) then
          begin
            C.B := (Clamp(Unpremultiply(Cb, Alpha), 0, MappingTablePrecicionMax2) + MappingTablePrecicionRound) shr MappingTablePrecicionShift2;
            C.G := (Clamp(Unpremultiply(Cg, Alpha), 0, MappingTablePrecicionMax2) + MappingTablePrecicionRound) shr MappingTablePrecicionShift2;
            C.R := (Clamp(Unpremultiply(Cr, Alpha), 0, MappingTablePrecicionMax2) + MappingTablePrecicionRound) shr MappingTablePrecicionShift2;
            C.A := Alpha;
          end else
          begin
            C.B := (Clamp(Cb, 0, MappingTablePrecicionMax2) + MappingTablePrecicionRound) shr MappingTablePrecicionShift2;
            C.G := (Clamp(Cg, 0, MappingTablePrecicionMax2) + MappingTablePrecicionRound) shr MappingTablePrecicionShift2;
            C.R := (Clamp(Cr, 0, MappingTablePrecicionMax2) + MappingTablePrecicionRound) shr MappingTablePrecicionShift2;
            C.A := 255; // We know Alpha=255 because RangeCheck is True otherwise
          end;
        end else
          C.ARGB := 0;
{$else PREMULTIPLY}
        if (Ca <> 0) then
        begin
          C.B := (Clamp(Cb, 0, MappingTablePrecicionMax2) + MappingTablePrecicionRound) shr MappingTablePrecicionShift2;
          C.G := (Clamp(Cg, 0, MappingTablePrecicionMax2) + MappingTablePrecicionRound) shr MappingTablePrecicionShift2;
          C.R := (Clamp(Cr, 0, MappingTablePrecicionMax2) + MappingTablePrecicionRound) shr MappingTablePrecicionShift2;
          C.A := (Clamp(Ca, 0, MappingTablePrecicionMax2) + MappingTablePrecicionRound) shr MappingTablePrecicionShift2;
        end else
          C.ARGB := 0;
{$endif PREMULTIPLY}
      end else
      begin
{$ifdef PREMULTIPLY}
        Alpha:= (Ca + MappingTablePrecicionRound) shr MappingTablePrecicionShift2;
        if (Alpha <> 0) then
        begin
          C.B := (Cb + MappingTablePrecicionRound) shr MappingTablePrecicionShift2;
          C.G := (Cg + MappingTablePrecicionRound) shr MappingTablePrecicionShift2;
          C.R := (Cr + MappingTablePrecicionRound) shr MappingTablePrecicionShift2;
          C.A := 255; // We know Alpha=255 because RangeCheck is True otherwise
        end else
          C.ARGB := 0;
{$else PREMULTIPLY}
        if (Ca <> 0) then
        begin
          C.B := (Cb + MappingTablePrecicionRound) shr MappingTablePrecicionShift2;
          C.G := (Cg + MappingTablePrecicionRound) shr MappingTablePrecicionShift2;
          C.R := (Cr + MappingTablePrecicionRound) shr MappingTablePrecicionShift2;
          C.A := (Ca + MappingTablePrecicionRound) shr MappingTablePrecicionShift2;
        end else
          C.ARGB := 0;
{$endif PREMULTIPLY}
      end;

      // Combine it with the background
      case CombineOp of
        dmOpaque:
          DstLine[I] := C.ARGB;

        dmBlend:
          BlendMemEx(C.ARGB, DstLine[I], Src.MasterAlpha);

        dmTransparent:
          if C.ARGB <> Src.OuterColor then
            DstLine[I] := C.ARGB;

        dmCustom:
          CombineCallBack(C.ARGB, DstLine[I], Src.MasterAlpha);
      end;
    end;
  end;
end;


//------------------------------------------------------------------------------
//
//      DraftResample
//
//------------------------------------------------------------------------------
// Used by TDraftResampler.Resample
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
// BlockAverage_Pas
//------------------------------------------------------------------------------
function BlockAverage_Pas(Dlx, Dly: Cardinal; RowSrc: PColor32; OffSrc: Cardinal): TColor32;
var
 C: PColor32Entry;
 ix, iy, iA, iR, iG, iB, Area: Cardinal;
begin
  iR := 0;  iB := iR;  iG := iR;  iA := iR;
  for iy := 1 to Dly do
  begin
    C := PColor32Entry(RowSrc);
    for ix := 1 to Dlx do
    begin
      Inc(iB, C.B);
      Inc(iG, C.G);
      Inc(iR, C.R);
      Inc(iA, C.A);
      Inc(C);
    end;
    Inc(PByte(RowSrc), OffSrc);
  end;

  Area := Dlx * Dly;
  Area := $1000000 div Area;
  Result := iA * Area and $FF000000 or
            iR * Area shr  8 and $FF0000 or
            iG * Area shr 16 and $FF00 or
            iB * Area shr 24 and $FF;
end;


//------------------------------------------------------------------------------
// BlockAverage_SSE2
//------------------------------------------------------------------------------
{$if (not defined(PUREPASCAL)) and (not defined(OMIT_SSE2))}
function BlockAverage_SSE2(Dlx, Dly: Cardinal; RowSrc: PColor32; OffSrc: Cardinal): TColor32;
asm
{$IFDEF TARGET_X64}
        MOV        EAX,ECX
        MOV        R10D,EDX

        SHL        EAX,$02
        SUB        R9D,EAX

        PXOR       XMM1,XMM1
        PXOR       XMM2,XMM2
        PXOR       XMM7,XMM7

@@LoopY:
        MOV        EAX,ECX
        PXOR       XMM0,XMM0
        LEA        R8,[R8+RAX*4]
        NEG        RAX
@@LoopX:
        MOVD       XMM6,[R8+RAX*4]
        PUNPCKLBW  XMM6,XMM7
        PADDW      XMM0,XMM6
        INC        RAX
        JNZ        @@LoopX

        MOVQ       XMM6,XMM0
        PUNPCKLWD  XMM6,XMM7
        PADDD      XMM1,XMM6
        ADD        R8,R9
        DEC        EDX
        JNZ        @@LoopY

        MOV        EAX, ECX
        MUL        R10D
        MOV        ECX,EAX
        MOV        EAX,$01000000
        DIV        ECX
        MOV        ECX,EAX

        MOVD       EAX,XMM1
        MUL        ECX
        SHR        EAX,$18
        MOV        R10D,EAX

        SHUFPS     XMM1,XMM1,$39
        MOVD       EAX,XMM1
        MUL        ECX
        SHR        EAX,$10
        AND        EAX,$0000FF00
        ADD        R10D,EAX

        PSHUFD     XMM1,XMM1,$39
        MOVD       EAX,XMM1
        MUL        ECX
        SHR        EAX,$08
        AND        EAX,$00FF0000
        ADD        R10D,EAX

        PSHUFD     XMM1,XMM1,$39
        MOVD       EAX,XMM1
        MUL        ECX
        AND        EAX,$FF000000
        ADD        EAX,R10D
{$ELSE}
        PUSH       EBX
        PUSH       ESI
        PUSH       EDI

        MOV        EBX,OffSrc
        MOV        ESI,EAX
        MOV        EDI,EDX

        SHL        ESI,$02
        SUB        EBX,ESI

        PXOR       XMM1,XMM1
        PXOR       XMM2,XMM2
        PXOR       XMM7,XMM7

@@LoopY:
        MOV        ESI,EAX
        PXOR       XMM0,XMM0
        LEA        ECX,[ECX+ESI*4]
        NEG        ESI
@@LoopX:
        MOVD       XMM6,[ECX+ESI*4]
        PUNPCKLBW  XMM6,XMM7
        PADDW      XMM0,XMM6
        INC        ESI
        JNZ        @@LoopX

        MOVQ       XMM6,XMM0
        PUNPCKLWD  XMM6,XMM7
        PADDD      XMM1,XMM6
        ADD        ECX,EBX
        DEC        EDX
        JNZ        @@LoopY

        MUL        EDI
        MOV        ECX,EAX
        MOV        EAX,$01000000
        DIV        ECX
        MOV        ECX,EAX

        MOVD       EAX,XMM1
        MUL        ECX
        SHR        EAX,$18
        MOV        EDI,EAX

        SHUFPS     XMM1,XMM1,$39
        MOVD       EAX,XMM1
        MUL        ECX
        SHR        EAX,$10
        AND        EAX,$0000FF00
        ADD        EDI,EAX

        PSHUFD     XMM1,XMM1,$39
        MOVD       EAX,XMM1
        MUL        ECX
        SHR        EAX,$08
        AND        EAX,$00FF0000
        ADD        EDI,EAX

        PSHUFD     XMM1,XMM1,$39
        MOVD       EAX,XMM1
        MUL        ECX
        AND        EAX,$FF000000
        ADD        EAX,EDI

        POP        EDI
        POP        ESI
        POP        EBX
{$ENDIF}
end;
{$ifend}


//------------------------------------------------------------------------------
// DraftResample
//------------------------------------------------------------------------------
procedure DraftResample(Dst: TCustomBitmap32; DstRect: TRect; DstClip: TRect;
  Src: TCustomBitmap32; SrcRect: TRect; Kernel: TCustomKernel;
  CombineOp: TDrawMode; CombineCallBack: TPixelCombineEvent);
var
  SrcW, SrcH,
  DstW, DstH,
  DstClipW, DstClipH: Cardinal;
  RowSrc: PColor32;
  xsrc: PColor32;
  OffSrc,
  dy, dx,
  c1, c2, r1, r2,
  xs: Cardinal;
  C: TColor32;
  DstLine: PColor32Array;
  ScaleFactor: TFloat;
  I,J, sc, sr, cx, cy: Integer;
  BlendMemEx: TBlendMemEx;
begin
 { rangechecking and rect intersection done by caller }

  SrcW := SrcRect.Right  - SrcRect.Left;
  SrcH := SrcRect.Bottom - SrcRect.Top;

  DstW := DstRect.Right  - DstRect.Left;
  DstH := DstRect.Bottom - DstRect.Top;

  DstClipW := DstClip.Right - DstClip.Left;
  DstClipH := DstClip.Bottom - DstClip.Top;

  BlendMemEx := BLEND_MEM_EX[Src.CombineMode]^;

  if (DstW > SrcW)or(DstH > SrcH) then
  begin
    if (SrcW < 2) or (SrcH < 2) then
      Resample(Dst, DstRect, DstClip, Src, SrcRect, Kernel, CombineOp, CombineCallBack)
    else
      StretchHorzStretchVertLinear(Dst, DstRect, DstClip, Src, SrcRect, CombineOp, CombineCallBack);
  end else
  begin //Full Scaledown, ignores Fulledge - cannot be integrated into this resampling method
    OffSrc := Src.Width * 4;

    ScaleFactor:= SrcW / DstW;
    cx := Trunc( (DstClip.Left - DstRect.Left) * ScaleFactor);
    r2 := Trunc(ScaleFactor);
    sr := Trunc( $10000 * ScaleFactor );

    ScaleFactor:= SrcH / DstH;
    cy := Trunc( (DstClip.Top - DstRect.Top) * ScaleFactor);
    c2 := Trunc(ScaleFactor);
    sc := Trunc( $10000 * ScaleFactor );

    DstLine := PColor32Array(Dst.PixelPtr[0, DstClip.Top]);
    RowSrc := Src.PixelPtr[SrcRect.Left +  cx, SrcRect.Top + cy ];

    xs := r2;
    c1 := 0;
    Dec(DstClip.Left, 2);
    Inc(DstClipW);
    Inc(DstClipH);

    for J := 2  to DstClipH do
    begin
      dy := c2 - c1;
      c1 := c2;
      c2 := FixedMul(J, sc);
      r1 := 0;
      r2 := xs;
      xsrc := RowSrc;

      case CombineOp of
        dmOpaque:
          for I := 2  to DstClipW do
          begin
            dx := r2 - r1;  r1 := r2;
            r2 := FixedMul(I, sr);
            DstLine[DstClip.Left + I] := BlockAverage(dx, dy, xsrc, OffSrc);
            Inc(xsrc, dx);
          end;
        dmBlend:
          for I := 2  to DstClipW do
          begin
            dx := r2 - r1;  r1 := r2;
            r2 := FixedMul(I, sr);
            BlendMemEx(BlockAverage(dx, dy, xsrc, OffSrc),
              DstLine[DstClip.Left + I], Src.MasterAlpha);
            Inc(xsrc, dx);
          end;
        dmTransparent:
          for I := 2  to DstClipW do
          begin
            dx := r2 - r1;  r1 := r2;
            r2 := FixedMul(I, sr);
            C := BlockAverage(dx, dy, xsrc, OffSrc);
            if C <> Src.OuterColor then DstLine[DstClip.Left + I] := C;
            Inc(xsrc, dx);
          end;
        dmCustom:
          for I := 2  to DstClipW do
          begin
            dx := r2 - r1;  r1 := r2;
            r2 := FixedMul(I, sr);
            CombineCallBack(BlockAverage(dx, dy, xsrc, OffSrc),
              DstLine[DstClip.Left + I], Src.MasterAlpha);
            Inc(xsrc, dx);
          end;
      end;

      Inc(DstLine, Dst.Width);
      Inc(PByte(RowSrc), OffSrc * dy);
    end;
  end;
end;


//------------------------------------------------------------------------------
//
//      Special interpolators (for sfLinear and sfDraft)
//
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
// Interpolator_Pas
//------------------------------------------------------------------------------
function Interpolator_Pas(WX_256, WY_256: Cardinal; C11, C21: PColor32): TColor32;
var
  C1, C3: TColor32;
begin
  if WX_256 > $FF then WX_256:= $FF;
  if WY_256 > $FF then WY_256:= $FF;
  C1 := C11^; Inc(C11);
  C3 := C21^; Inc(C21);
  Result := CombineReg(CombineReg(C1, C11^, WX_256),
                       CombineReg(C3, C21^, WX_256), WY_256);
end;


//------------------------------------------------------------------------------
// Interpolator_SSE2
//------------------------------------------------------------------------------
{$if (not defined(PUREPASCAL)) and (not defined(OMIT_SSE2))}
function Interpolator_SSE2(WX_256, WY_256: Cardinal; C11, C21: PColor32): TColor32;
asm
{$IFDEF TARGET_X64}
        MOV       RAX, RCX
        MOVQ      XMM1,QWORD PTR [R8]
        MOVQ      XMM2,XMM1
        MOVQ      XMM3,QWORD PTR [R9]
{$ELSE}
        MOVQ      XMM1,[ECX]
        MOVQ      XMM2,XMM1
        MOV       ECX,C21
        MOVQ      XMM3,[ECX]
{$ENDIF}
        PSRLQ     XMM1,32
        MOVQ      XMM4,XMM3
        PSRLQ     XMM3,32
        MOVD      XMM5,EAX
        PSHUFLW   XMM5,XMM5,0
        PXOR      XMM0,XMM0
        PUNPCKLBW XMM1,XMM0
        PUNPCKLBW XMM2,XMM0
        PSUBW     XMM2,XMM1
        PMULLW    XMM2,XMM5
        PSLLW     XMM1,8
        PADDW     XMM2,XMM1
        PSRLW     XMM2,8
        PUNPCKLBW XMM3,XMM0
        PUNPCKLBW XMM4,XMM0
        PSUBW     XMM4,XMM3
        PSLLW     XMM3,8
        PMULLW    XMM4,XMM5
        PADDW     XMM4,XMM3
        PSRLW     XMM4,8
        MOVD      XMM5,EDX
        PSHUFLW   XMM5,XMM5,0
        PSUBW     XMM2,XMM4
        PMULLW    XMM2,XMM5
        PSLLW     XMM4,8
        PADDW     XMM2,XMM4
        PSRLW     XMM2,8
        PACKUSWB  XMM2,XMM0
        MOVD      EAX,XMM2
end;
{$ifend}


//------------------------------------------------------------------------------
//
//      StretchTransfer
//
//------------------------------------------------------------------------------
{$WARNINGS OFF}
procedure StretchTransfer(
  Dst: TCustomBitmap32; DstRect: TRect; DstClip: TRect;
  Src: TCustomBitmap32; SrcRect: TRect;
  Resampler: TCustomResampler;
  CombineOp: TDrawMode; CombineCallBack: TPixelCombineEvent);
var
  SrcW, SrcH: Integer;
  DstW, DstH: Integer;
  R: TRect;
  RatioX, RatioY: Single;
begin
  CheckBitmaps(Dst, Src);

  // transform dest rect when the src rect is out of the src bitmap's bounds
  if (SrcRect.Left < 0) or (SrcRect.Right > Src.Width) or
    (SrcRect.Top < 0) or (SrcRect.Bottom > Src.Height) then
  begin
    RatioX := (DstRect.Right - DstRect.Left) / (SrcRect.Right - SrcRect.Left);
    RatioY := (DstRect.Bottom - DstRect.Top) / (SrcRect.Bottom - SrcRect.Top);

    if SrcRect.Left < 0 then
    begin
      DstRect.Left := DstRect.Left + Ceil(-SrcRect.Left * RatioX);
      SrcRect.Left := 0;
    end;

    if SrcRect.Top < 0 then
    begin
      DstRect.Top := DstRect.Top + Ceil(-SrcRect.Top * RatioY);
      SrcRect.Top := 0;
    end;

    if SrcRect.Right > Src.Width then
    begin
      DstRect.Right := DstRect.Right - Floor((SrcRect.Right - Src.Width) * RatioX);
      SrcRect.Right := Src.Width;
    end;

    if SrcRect.Bottom > Src.Height then
    begin
      DstRect.Bottom := DstRect.Bottom - Floor((SrcRect.Bottom - Src.Height) * RatioY);
      SrcRect.Bottom := Src.Height;
    end;
  end;

  if Src.Empty or Dst.Empty or
    ((CombineOp = dmBlend) and (Src.MasterAlpha = 0)) or
    GR32.IsRectEmpty(SrcRect) then
      Exit;

  if not Dst.MeasuringMode then
  begin
    GR32.IntersectRect(DstClip, DstClip, Dst.BoundsRect);
    GR32.IntersectRect(DstClip, DstClip, DstRect);
    if GR32.IsRectEmpty(DstClip) then Exit;
    GR32.IntersectRect(R, DstClip, DstRect);
    if GR32.IsRectEmpty(R) then Exit;

    if (CombineOp = dmCustom) and not Assigned(CombineCallBack) then
      CombineOp := dmOpaque;

    SrcW := SrcRect.Right - SrcRect.Left;
    SrcH := SrcRect.Bottom - SrcRect.Top;
    DstW := DstRect.Right - DstRect.Left;
    DstH := DstRect.Bottom - DstRect.Top;

    if (SrcW = DstW) and (SrcH = DstH) then
      BlendBlock(Dst, DstClip, Src, SrcRect.Left + DstClip.Left - DstRect.Left, SrcRect.Top + DstClip.Top - DstRect.Top, CombineOp, CombineCallBack)
    else
      TCustomResamplerAccess(Resampler).Resample(Dst, DstRect, DstClip, Src, SrcRect, CombineOp, CombineCallBack);
  end;

  Dst.Changed(DstRect);
end;
{$WARNINGS ON}


//------------------------------------------------------------------------------
//
//      TByteMap downsample functions
//
//------------------------------------------------------------------------------
procedure DownsampleByteMap2x(Source, Dest: TByteMap);
var
  X, Y: Integer;
  ScnLn: array [0 .. 2] of PByteArray;
begin
  for Y := 0 to (Source.Height div 2) - 1 do
  begin
    ScnLn[0] := Dest.ScanLine[Y];
    ScnLn[1] := Source.ScanLine[Y * 2];
    ScnLn[2] := Source.ScanLine[Y * 2 + 1];
    for X := 0 to (Source.Width div 2) - 1 do
      ScnLn[0, X] := (
        ScnLn[1, 2 * X] + ScnLn[1, 2 * X + 1] +
        ScnLn[2, 2 * X] + ScnLn[2, 2 * X + 1]) div 4;
  end;
end;

//------------------------------------------------------------------------------

procedure DownsampleByteMap3x(Source, Dest: TByteMap);
var
  X, Y: Integer;
  x3: Integer;
  ScnLn: array [0 .. 3] of PByteArray;
begin
  for Y := 0 to (Source.Height div 3) - 1 do
  begin
    ScnLn[0] := Dest.ScanLine[Y];
    ScnLn[1] := Source.ScanLine[3 * Y];
    ScnLn[2] := Source.ScanLine[3 * Y + 1];
    ScnLn[3] := Source.ScanLine[3 * Y + 2];
    for X := 0 to (Source.Width div 3) - 1 do
    begin
      x3 := 3 * X;
      ScnLn[0, X] := (
        ScnLn[1, x3] + ScnLn[1, x3 + 1] + ScnLn[1, x3 + 2] +
        ScnLn[2, x3] + ScnLn[2, x3 + 1] + ScnLn[2, x3 + 2] +
        ScnLn[3, x3] + ScnLn[3, x3 + 1] + ScnLn[3, x3 + 2]) div 9;
    end;
  end;
end;

//------------------------------------------------------------------------------

procedure DownsampleByteMap4x(Source, Dest: TByteMap);
var
  X, Y: Integer;
  x4: Integer;
  ScnLn: array [0 .. 4] of PByteArray;
begin
  for Y := 0 to (Source.Height div 4) - 1 do
  begin
    ScnLn[0] := Dest.ScanLine[Y];
    ScnLn[1] := Source.ScanLine[Y * 4];
    ScnLn[2] := Source.ScanLine[Y * 4 + 1];
    ScnLn[3] := Source.ScanLine[Y * 4 + 2];
    ScnLn[4] := Source.ScanLine[Y * 4 + 3];
    for X := 0 to (Source.Width div 4) - 1 do
    begin
      x4 := 4 * X;
      ScnLn[0, X] := (
        ScnLn[1, x4] + ScnLn[1, x4 + 1] + ScnLn[1, x4 + 2] + ScnLn[1, x4 + 3] +
        ScnLn[2, x4] + ScnLn[2, x4 + 1] + ScnLn[2, x4 + 2] + ScnLn[2, x4 + 3] +
        ScnLn[3, x4] + ScnLn[3, x4 + 1] + ScnLn[3, x4 + 2] + ScnLn[3, x4 + 3] +
        ScnLn[4, x4] + ScnLn[4, x4 + 1] + ScnLn[4, x4 + 2] + ScnLn[4, x4 + 3]) div 16;
    end;
  end;
end;


//------------------------------------------------------------------------------
//
//      TCustomKernel
//
//------------------------------------------------------------------------------
procedure TCustomKernel.AssignTo(Dst: TPersistent);
begin
  if Dst is TCustomKernel then
    SmartAssign(Self, Dst)
  else
    inherited;
end;

procedure TCustomKernel.Changed;
begin
  if Assigned(FObserver) then FObserver.Changed;
end;

constructor TCustomKernel.Create;
begin
end;

function TCustomKernel.RangeCheck: Boolean;
begin
  Result := False;
end;


//------------------------------------------------------------------------------
//
//      TBoxKernel
//
//------------------------------------------------------------------------------
function TBoxKernel.Filter(Value: TFloat): TFloat;
begin
  if (Value >= -0.5) and (Value <= 0.5) then
    Result := 1.0
  else
    Result := 0;
end;

function TBoxKernel.GetWidth: TFloat;
begin
  Result := 1;
end;


//------------------------------------------------------------------------------
//
//      TLinearKernel
//
//------------------------------------------------------------------------------
function TLinearKernel.Filter(Value: TFloat): TFloat;
begin
  if Value < -1 then
    Result := 0
  else
  if Value < 0 then
    Result := 1 + Value
  else
  if Value < 1 then
    Result := 1 - Value
  else
    Result := 0;
end;

function TLinearKernel.GetWidth: TFloat;
begin
  Result := 1;
end;


//------------------------------------------------------------------------------
//
//      TCosineKernel
//
//------------------------------------------------------------------------------
function TCosineKernel.Filter(Value: TFloat): TFloat;
begin
  Result := 0;
  if Abs(Value) < 1 then
    Result := (Cos(Value * Pi) + 1) * 0.5;
end;

function TCosineKernel.GetWidth: TFloat;
begin
  Result := 1;
end;


//------------------------------------------------------------------------------
//
//      TSplineKernel
//
//------------------------------------------------------------------------------
function TSplineKernel.Filter(Value: TFloat): TFloat;
var
  tt: TFloat;
const
  TwoThirds = 2 / 3;
  OneSixth = 1 / 6;
begin
  Value := Abs(Value);
  if Value < 1 then
  begin
    tt := Sqr(Value);
    Result := 0.5 * tt * Value - tt + TwoThirds;
  end
  else if Value < 2 then
  begin
    Value := 2 - Value;
    Result := OneSixth * Sqr(Value) * Value;
  end
  else Result := 0;
end;

function TSplineKernel.RangeCheck: Boolean;
begin
  Result := True;
end;

function TSplineKernel.GetWidth: TFloat;
begin
  Result := 2;
end;


//------------------------------------------------------------------------------
//
//      TMitchellKernel
//
//------------------------------------------------------------------------------
function TMitchellKernel.Filter(Value: TFloat): TFloat;
var
  tt, ttt: TFloat;
const
  OneEighteenth = 1 / 18;
begin
  Value := Abs(Value);
  tt := Sqr(Value);
  ttt := tt * Value;

  // Given B = C = 1/3

  if Value < 1 then
    // ((((12 - 9 * B - 6 * C) * ttt) + ((-18 + 12 * B + 6 * C) * tt) + (6 - 2 * B))) / 6
    Result := (21 * ttt - 36 * tt + 16 ) * OneEighteenth
  else
  if Value < 2 then
    // ((((-1 * B - 6 * C) * ttt) + ((6 * B + 30 * C) * tt) + ((-12 * B - 48 * C) * Value) + (8 * B + 24 * C))) / 6
    Result := (- 7 * ttt + 36 * tt - 60 * Value + 32) * OneEighteenth
  else
    Result := 0;
end;

function TMitchellKernel.RangeCheck: Boolean;
begin
  Result := True;
end;

function TMitchellKernel.GetWidth: TFloat;
begin
  Result := 2;
end;


//------------------------------------------------------------------------------
//
//      TCubicKernel
//
//------------------------------------------------------------------------------
constructor TCubicKernel.Create;
begin
  FCoeff := -0.5;
end;

function TCubicKernel.Filter(Value: TFloat): TFloat;
var
  tt, ttt: TFloat;
begin
  Value := Abs(Value);
  tt := Sqr(Value);
  ttt := tt * Value;
  if Value <= 1 then
    Result := (FCoeff + 2) * ttt - (FCoeff + 3) * tt + 1
  else
  if Value < 2 then
    Result := FCoeff * (ttt - 5 * tt + 8 * Value - 4)
  else
    Result := 0;
end;

function TCubicKernel.RangeCheck: Boolean;
begin
  Result := True;
end;

function TCubicKernel.GetWidth: TFloat;
begin
  Result := 2;
end;

procedure TCubicKernel.SetCoeff(const Value: TFloat);
begin
  if Value <> FCoeff then
  begin
    FCoeff := Value;
    Changed;
  end
end;


//------------------------------------------------------------------------------
//
//      THermiteKernel
//
//------------------------------------------------------------------------------
constructor THermiteKernel.Create;
begin
  FBias := 0;
  FTension := 0;
end;

function THermiteKernel.Filter(Value: TFloat): TFloat;
var
  Z: Integer;
  t, t2, t3, m0, m1, a0, a1, a2, a3: TFloat;
begin
  t := (1 - FTension) * 0.5;
  m0 := (1 + FBias) * t;
  m1 := (1 - FBias) * t;

  Z := Floor(Value);
  t := Abs(Z - Value);
  t2 := t * t;
  t3 := t2 * t;

  a1 := t3 - 2 * t2 + t;
  a2 := t3 - t2;
  a3 := -2 * t3 + 3 * t2;
  a0 := -a3 + 1;

  case Z of
    -2: Result := a2 * m1;
    -1: Result := a3 + a1 * m1 + a2 * (m0 - m1);
     0: Result := a0 + a1 * (m0 - m1) - a2 * m0;
     1: Result := -a1 * m0;
  else
    Result := 0;
  end;
end;

function THermiteKernel.GetWidth: TFloat;
begin
  Result := 2;
end;

function THermiteKernel.RangeCheck: Boolean;
begin
  Result := True;
end;

procedure THermiteKernel.SetBias(const Value: TFloat);
begin
  if FBias <> Value then
  begin
    FBias := Value;
    Changed;
  end;
end;

procedure THermiteKernel.SetTension(const Value: TFloat);
begin
  if FTension <> Value then
  begin
    FTension := Value;
    Changed;
  end;
end;


//------------------------------------------------------------------------------
//
//      TSinshKernel
//
//------------------------------------------------------------------------------
constructor TSinshKernel.Create;
begin
  FWidth := 3;
  FCoeff := 0.5;
end;

function TSinshKernel.Filter(Value: TFloat): TFloat;
begin
  if Value = 0 then
    Result := 1
  else
    Result := FCoeff * Sin(Pi * Value) / Sinh(Pi * FCoeff * Value);
end;

function TSinshKernel.RangeCheck: Boolean;
begin
  Result := True;
end;

procedure TSinshKernel.SetWidth(Value: TFloat);
begin
  if FWidth <> Value then
  begin
    FWidth := Value;
    Changed;
  end;
end;

function TSinshKernel.GetWidth: TFloat;
begin
  Result := FWidth;
end;

procedure TSinshKernel.SetCoeff(const Value: TFloat);
begin
  if (FCoeff <> Value) and (FCoeff <> 0) then
  begin
    FCoeff := Value;
    Changed;
  end;
end;


//------------------------------------------------------------------------------
//
//      TWindowedKernel
//
//------------------------------------------------------------------------------
procedure TWindowedKernel.DoSetWidth(Value: TFloat);
begin
  FWidth := Value;
  FWidthReciprocal := 1 / FWidth;
end;

function TWindowedKernel.Filter(Value: TFloat): TFloat;
begin
  Value := Abs(Value);
  if Value < FWidth then
    Result := Window(Value)
  else
    Result := 0;
end;

function TWindowedKernel.RangeCheck: Boolean;
begin
  Result := True;
end;

procedure TWindowedKernel.SetWidth(Value: TFloat);
begin
  Value := Min(MAX_KERNEL_WIDTH, Value);
  if Value <> FWidth then
  begin
    DoSetWidth(Value);
    Changed;
  end;
end;

function TWindowedKernel.GetWidth: TFloat;
begin
  Result := FWidth;
end;


//------------------------------------------------------------------------------
//
//      TGaussianKernel
//
//------------------------------------------------------------------------------
const
  // Because the gaussian function has inifinite extent we need to limit the
  // width of the window to something reasonable.
  // Often the limit (width) is set to "Full Width at Half Maximum" (FWHM) by
  // calculing the ratio between Radius and Sigma as
  //
  //   Ratio = 1 / FWHM
  //         = 1 / (2 * Sqrt(2 * Ln(2)))
  //         = 0.424660891294479
  //
  // however, for resampling we need the area of the curve covered by the window
  // to be as close to 1 as possible so instead we calculate the ratio so that
  //
  //   Ceil(Sigma / Ratio)
  //
  // gives us the smallest size of a kernel containing values >= 1/255:
  //
  //   Ratio = 1 / Sqrt(-2 * Ln(1/255))
  //         = 0.300386630413846
  //
  GaussianRadiusToSigma = 0.300386630413846;

  GaussianSigmaToRadius = 1 / GaussianRadiusToSigma;
  GaussianMinSigma = 0.4; // Sigma smaller than this causes overflow; Window(0) > 1

constructor TGaussianKernel.Create;
begin
  inherited;
  DoSetSigma(1 / Sqrt(2 * Pi));
end;

procedure TGaussianKernel.DoSetSigma(const Value: TFloat);
begin
  FSigma := Value;
  FSigmaReciprocal := -0.5 / Sqr(FSigma);
  FNormalizationFactor := 1 / (FSigma * Sqrt(2 * Pi));
  DoSetWidth(FSigma * GaussianSigmaToRadius);
end;

procedure TGaussianKernel.SetSigma(const Value: TFloat);
begin
  if (FSigma <> Value) and (FSigma <> 0) then
  begin
    DoSetSigma(Value);
    Changed;
  end;
end;

function TGaussianKernel.Window(Value: TFloat): TFloat;
begin
  (*
  **    Gauss(x, σ) = 1/(σ √ 2π) * e^( - x^2 / (2 * σ^2))
  **
  **    FNormalizationFactor = 1/(σ √ 2π)
  **
  **    FSigmaReciprocal = - 1 / (2 * σ^2)
  *)
  Result := FNormalizationFactor * Exp(Sqr(Value) * FSigmaReciprocal);
end;


//------------------------------------------------------------------------------
//
//      TWindowedSincKernel
//
//------------------------------------------------------------------------------
class function TWindowedSincKernel.Sinc(Value: TFloat): TFloat;
begin
  if Value <> 0 then
  begin
    Value := Value * Pi;
    Result := Sin(Value) / Value;
  end
  else Result := 1;
end;

constructor TWindowedSincKernel.Create;
begin
  inherited;
  FWidth := 3;
  FWidthReciprocal := 1 / FWidth;
end;

function TWindowedSincKernel.Filter(Value: TFloat): TFloat;
begin
  Value := Abs(Value);
  if Value < FWidth then
    Result := Sinc(Value) * Window(Value)
  else
    Result := 0;
end;


//------------------------------------------------------------------------------
//
//      TAlbrechtKernel
//
//------------------------------------------------------------------------------
constructor TAlbrechtKernel.Create;
begin
  inherited;
  Terms := 7;
end;

procedure TAlbrechtKernel.SetTerms(Value: Integer);
begin
  Value := Constrain(Value, 2, 11);
  if FTerms <> Value then
  begin
    FTerms := Value;

    case Value of
      2 : Move(CAlbrecht2 [0], FCoefPointer[0], Value * SizeOf(Double));
      3 : Move(CAlbrecht3 [0], FCoefPointer[0], Value * SizeOf(Double));
      4 : Move(CAlbrecht4 [0], FCoefPointer[0], Value * SizeOf(Double));
      5 : Move(CAlbrecht5 [0], FCoefPointer[0], Value * SizeOf(Double));
      6 : Move(CAlbrecht6 [0], FCoefPointer[0], Value * SizeOf(Double));
      7 : Move(CAlbrecht7 [0], FCoefPointer[0], Value * SizeOf(Double));
      8 : Move(CAlbrecht8 [0], FCoefPointer[0], Value * SizeOf(Double));
      9 : Move(CAlbrecht9 [0], FCoefPointer[0], Value * SizeOf(Double));
     10 : Move(CAlbrecht10[0], FCoefPointer[0], Value * SizeOf(Double));
     11 : Move(CAlbrecht11[0], FCoefPointer[0], Value * SizeOf(Double));
    end;

    Changed;
  end;
end;

function TAlbrechtKernel.Window(Value: TFloat): TFloat;
var
  cs : Double;
  i  : Integer;
begin
  cs := Cos(Pi * Value * FWidthReciprocal);
  i := FTerms - 1;
  Result := FCoefPointer[i];
  while i > 0 do
  begin
    Dec(i);
    Result := Result * cs + FCoefPointer[i];
  end;
end;


//------------------------------------------------------------------------------
//
//      TLanczosKernel
//
//------------------------------------------------------------------------------
function TLanczosKernel.Window(Value: TFloat): TFloat;
begin
  Result := Sinc(Value * FWidthReciprocal); // Get rid of division
end;


//------------------------------------------------------------------------------
//
//      TBlackmanKernel
//
//------------------------------------------------------------------------------
function TBlackmanKernel.Window(Value: TFloat): TFloat;
begin
  Value := Cos(Pi * Value * FWidthReciprocal);                // get rid of division
  Result := 0.34 + 0.5 * Value + 0.16 * sqr(Value);
end;


//------------------------------------------------------------------------------
//
//      THannKernel
//
//------------------------------------------------------------------------------
function THannKernel.Window(Value: TFloat): TFloat;
begin
  Result := 0.5 + 0.5 * Cos(Pi * Value * FWidthReciprocal);   // get rid of division
end;


//------------------------------------------------------------------------------
//
//      THammingKernel
//
//------------------------------------------------------------------------------
function THammingKernel.Window(Value: TFloat): TFloat;
begin
  Result := 0.54 + 0.46 * Cos(Pi * Value * FWidthReciprocal); // get rid of division
end;


//------------------------------------------------------------------------------
//
//      TKernelResampler
//
//------------------------------------------------------------------------------
constructor TKernelResampler.Create;
begin
  inherited;
  Kernel := TBoxKernel.Create;
  FTableSize := 32;
end;

destructor TKernelResampler.Destroy;
begin
  FKernel.Free;
  inherited;
end;

function TKernelResampler.GetKernelClassName: string;
begin
  Result := FKernel.ClassName;
end;

procedure TKernelResampler.SetKernelClassName(const Value: string);
var
  KernelClass: TCustomKernelClass;
  NewKernel: TCustomKernel;
begin
  if (Value <> '') and (FKernel.ClassName <> Value) and (KernelList <> nil) then
  begin
    KernelClass := TCustomKernelClass(KernelList.Find(Value));
    if (KernelClass <> nil) then
    begin
      NewKernel := KernelClass.Create;
      try
        SetKernel(NewKernel);
      except
        if (FKernel <> NewKernel) then
          NewKernel.Free;
        raise;
      end;
    end;
  end;
end;

procedure TKernelResampler.SetKernel(const Value: TCustomKernel);
begin
  if (Value <> nil) and (FKernel <> Value) then
  begin
    FreeAndNil(FKernel);
    FKernel := Value;
    TCustomKernelAccess(FKernel).FObserver := Self;
    Changed;
  end;
end;

procedure TKernelResampler.Resample(Dst: TCustomBitmap32; DstRect,
  DstClip: TRect; Src: TCustomBitmap32; SrcRect: TRect; CombineOp: TDrawMode;
  CombineCallBack: TPixelCombineEvent);
begin
  GR32_Resamplers.Resample(Dst, DstRect, DstClip, Src, SrcRect, FKernel, CombineOp, CombineCallBack);
end;

{$WARNINGS OFF}

function TKernelResampler.GetSampleFloat(X, Y: TFloat): TColor32;
var
  clX, clY: Integer;
  fracX, fracY: Integer;
  fracXS: TFloat absolute fracX;
  fracYS: TFloat absolute fracY;

  Filter: TFilterMethod;
  WrapProcVert: TWrapProcEx absolute Filter;
  WrapProcHorz: TWrapProcEx;
  Colors: PColor32EntryArray;
  KWidth, W, Wv, I, J, Incr, Dev: Integer;
  SrcP: PColor32Entry;
  C: TColor32Entry absolute SrcP;
  LoX, HiX, LoY, HiY, MappingY: Integer;

  HorzKernel, VertKernel: TKernelEntry;
  PHorzKernel, PVertKernel, FloorKernel, CeilKernel: PKernelEntry;

  HorzEntry, VertEntry: TBufferEntry;
  MappingX: TKernelEntry;
  Edge: Boolean;

  Alpha: integer;
  OuterPremultColorR, OuterPremultColorG, OuterPremultColorB: Byte;
begin
  KWidth := Ceil(FKernel.GetWidth);

  clX := Ceil(X);
  clY := Ceil(Y);

  case PixelAccessMode of
    pamUnsafe, pamWrap:
      begin
        LoX := -KWidth; HiX := KWidth;
        LoY := -KWidth; HiY := KWidth;
      end;

    pamSafe, pamTransparentEdge:
      begin
        with ClipRect do
        begin
          if not ((clX < Left) or (clX > Right) or (clY < Top) or (clY > Bottom)) then
          begin
            Edge := False;

            if clX - KWidth < Left then
            begin
              LoX := Left - clX;
              Edge := True;
            end
            else
              LoX := -KWidth;

            if clX + KWidth >= Right then
            begin
              HiX := Right - clX - 1;
              Edge := True;
            end
            else
              HiX := KWidth;

            if clY - KWidth < Top then
            begin
              LoY := Top - clY;
              Edge := True;
            end
            else
              LoY := -KWidth;

            if clY + KWidth >= Bottom then
            begin
              HiY := Bottom - clY - 1;
              Edge := True;
            end
            else
              HiY := KWidth;

          end
          else
          begin
            if PixelAccessMode = pamTransparentEdge then
              Result := 0
            else
              Result := FOuterColor;
            Exit;
          end;

        end;
      end;
  end;

  case FKernelMode of
    kmDynamic:
      begin
        Filter := FKernel.Filter;
        fracXS := clX - X;
        fracYS := clY - Y;

        PHorzKernel := @HorzKernel;
        PVertKernel := @VertKernel;

        Dev := -256;
        for I := -KWidth to KWidth do
        begin
          W := Round(Filter(I + fracXS) * 256);
          HorzKernel[I] := W;
          Inc(Dev, W);
        end;
        Dec(HorzKernel[0], Dev);

        Dev := -256;
        for I := -KWidth to KWidth do
        begin
          W := Round(Filter(I + fracYS) * 256);
          VertKernel[I] := W;
          Inc(Dev, W);
        end;
        Dec(VertKernel[0], Dev);

      end;

    kmTableNearest:
      begin
        W := FWeightTable.Height - 2;
        PHorzKernel := @FWeightTable.ValPtr[KWidth - MAX_KERNEL_WIDTH, Round((clX - X) * W)]^;
        PVertKernel := @FWeightTable.ValPtr[KWidth - MAX_KERNEL_WIDTH, Round((clY - Y) * W)]^;
      end;

    kmTableLinear:
      begin
        W := (FWeightTable.Height - 2) * $10000;
        J := FWeightTable.Width * 4;

        with TFixedRec(FracX) do
        begin
          Fixed := Round((clX - X) * W);
          PHorzKernel := @HorzKernel;
          FloorKernel := @FWeightTable.ValPtr[KWidth - MAX_KERNEL_WIDTH, Int]^;
          CeilKernel := PKernelEntry(NativeUInt(FloorKernel) + J);
          Dev := -256;
          for I := -KWidth to KWidth do
          begin
            Wv :=  FloorKernel[I] + ((CeilKernel[I] - FloorKernel[I]) * Frac + $7FFF) div FixedOne;
            HorzKernel[I] := Wv;
            Inc(Dev, Wv);
          end;
          Dec(HorzKernel[0], Dev);
        end;

        with TFixedRec(FracY) do
        begin
          Fixed := Round((clY - Y) * W);
          PVertKernel := @VertKernel;
          FloorKernel := @FWeightTable.ValPtr[KWidth - MAX_KERNEL_WIDTH, Int]^;
          CeilKernel := PKernelEntry(NativeUInt(FloorKernel) + J);
          Dev := -256;
          for I := -KWidth to KWidth do
          begin
            Wv := FloorKernel[I] + ((CeilKernel[I] - FloorKernel[I]) * Frac + $7FFF) div FixedOne;
            VertKernel[I] := Wv;
            Inc(Dev, Wv);
          end;
          Dec(VertKernel[0], Dev);
        end;
      end;

  end;

  VertEntry := Default(TBufferEntry);
  case PixelAccessMode of
    pamUnsafe, pamSafe, pamTransparentEdge:
      begin
        SrcP := PColor32Entry(Bitmap.PixelPtr[LoX + clX, LoY + clY]);
        Incr := Bitmap.Width - (HiX - LoX) - 1;
        for I := LoY to HiY do
        begin
          Wv := PVertKernel[I];
          if Wv <> 0 then
          begin
            HorzEntry := Default(TBufferEntry);
            for J := LoX to HiX do
            begin
              // Alpha=0 should not contribute to sample.
              Alpha := SrcP.A;
              if (Alpha <> 0) then
              begin
                W := PHorzKernel[J];
                Inc(HorzEntry.A, Alpha * W);
                // Sample premultiplied values
                if (Alpha = 255) then
                begin
                  Inc(HorzEntry.R, SrcP.R * W);
                  Inc(HorzEntry.G, SrcP.G * W);
                  Inc(HorzEntry.B, SrcP.B * W);
                end else
                begin
                  Inc(HorzEntry.R, Integer(Div255(Alpha * SrcP.R)) * W);
                  Inc(HorzEntry.G, Integer(Div255(Alpha * SrcP.G)) * W);
                  Inc(HorzEntry.B, Integer(Div255(Alpha * SrcP.B)) * W);
                end;
              end;
              Inc(SrcP);
            end;
            Inc(VertEntry.A, HorzEntry.A * Wv);
            Inc(VertEntry.R, HorzEntry.R * Wv);
            Inc(VertEntry.G, HorzEntry.G * Wv);
            Inc(VertEntry.B, HorzEntry.B * Wv);
          end else Inc(SrcP, HiX - LoX + 1);
          Inc(SrcP, Incr);
        end;

        if (PixelAccessMode = pamSafe) and Edge then
        begin
          Alpha := TColor32Entry(FOuterColor).A;

          // Alpha=0 should not contribute to sample.
          if (Alpha <> 0) then
          begin
            // Sample premultiplied values
            OuterPremultColorR := Integer(Div255(Alpha * TColor32Entry(FOuterColor).R));
            OuterPremultColorG := Integer(Div255(Alpha * TColor32Entry(FOuterColor).G));
            OuterPremultColorB := Integer(Div255(Alpha * TColor32Entry(FOuterColor).B));

            for I := -KWidth to KWidth do
            begin
              Wv := PVertKernel[I];
              if Wv <> 0 then
              begin
                HorzEntry := Default(TBufferEntry);
                for J := -KWidth to KWidth do
                  if (J < LoX) or (J > HiX) or (I < LoY) or (I > HiY) then
                  begin
                    W := PHorzKernel[J];
                    Inc(HorzEntry.A, Alpha * W);
                    Inc(HorzEntry.R, OuterPremultColorR * W);
                    Inc(HorzEntry.G, OuterPremultColorG * W);
                    Inc(HorzEntry.B, OuterPremultColorB * W);
                  end;
                Inc(VertEntry.A, HorzEntry.A * Wv);
                Inc(VertEntry.R, HorzEntry.R * Wv);
                Inc(VertEntry.G, HorzEntry.G * Wv);
                Inc(VertEntry.B, HorzEntry.B * Wv);
              end;
            end
          end;
        end;
      end;

    pamWrap:
      begin
        WrapProcHorz := GetWrapProcEx(Bitmap.WrapMode, ClipRect.Left, ClipRect.Right - 1);
        WrapProcVert := GetWrapProcEx(Bitmap.WrapMode, ClipRect.Top, ClipRect.Bottom - 1);

        for I := -KWidth to KWidth do
          MappingX[I] := WrapProcHorz(clX + I, ClipRect.Left, ClipRect.Right - 1);

        for I := -KWidth to KWidth do
        begin
          Wv := PVertKernel[I];
          if Wv <> 0 then
          begin
            MappingY := WrapProcVert(clY + I, ClipRect.Top, ClipRect.Bottom - 1);
            Colors := PColor32EntryArray(Bitmap.ScanLine[MappingY]);
            HorzEntry := Default(TBufferEntry);
            for J := -KWidth to KWidth do
            begin
              C := Colors[MappingX[J]];
              Alpha := C.A;
              // Alpha=0 should not contribute to sample.
              if (Alpha <> 0) then
              begin
                W := PHorzKernel[J];
                Inc(HorzEntry.A, Alpha * W);
                // Sample premultiplied values
                if (Alpha = 255) then
                begin
                  Inc(HorzEntry.R, C.R * W);
                  Inc(HorzEntry.G, C.G * W);
                  Inc(HorzEntry.B, C.B * W);
                end else
                begin
                  Inc(HorzEntry.R, Div255(Alpha * C.R) * W);
                  Inc(HorzEntry.G, Div255(Alpha * C.G) * W);
                  Inc(HorzEntry.B, Div255(Alpha * C.B) * W);
                end;
              end;
            end;
            Inc(VertEntry.A, HorzEntry.A * Wv);
            Inc(VertEntry.R, HorzEntry.R * Wv);
            Inc(VertEntry.G, HorzEntry.G * Wv);
            Inc(VertEntry.B, HorzEntry.B * Wv);
          end;
        end;
      end;
  end;

  // Round and unpremultiply result
  with TColor32Entry(Result) do
  begin
    if FKernel.RangeCheck then
    begin
      A := Clamp(TFixedRec(Integer(VertEntry.A + FixedHalf)).Int);
      if (A = 255) then
      begin
        R := Clamp(TFixedRec(Integer(VertEntry.R + FixedHalf)).Int);
        G := Clamp(TFixedRec(Integer(VertEntry.G + FixedHalf)).Int);
        B := Clamp(TFixedRec(Integer(VertEntry.B + FixedHalf)).Int);
      end else
      if (A <> 0) then
      begin
        R := Clamp(TFixedRec(Integer(VertEntry.R + FixedHalf)).Int * 255 div A);
        G := Clamp(TFixedRec(Integer(VertEntry.G + FixedHalf)).Int * 255 div A);
        B := Clamp(TFixedRec(Integer(VertEntry.B + FixedHalf)).Int * 255 div A);
      end else
      begin
        R := 0;
        G := 0;
        B := 0;
      end;
    end
    else
    begin
      A := TFixedRec(Integer(VertEntry.A + FixedHalf)).Int;
      if (A = 255) then
      begin
        R := TFixedRec(Integer(VertEntry.R + FixedHalf)).Int;
        G := TFixedRec(Integer(VertEntry.G + FixedHalf)).Int;
        B := TFixedRec(Integer(VertEntry.B + FixedHalf)).Int;
      end else
      if (A <> 0) then
      begin
        R := TFixedRec(Integer(VertEntry.R + FixedHalf)).Int * 255 div A;
        G := TFixedRec(Integer(VertEntry.G + FixedHalf)).Int * 255 div A;
        B := TFixedRec(Integer(VertEntry.B + FixedHalf)).Int * 255 div A;
      end else
      begin
        R := 0;
        G := 0;
        B := 0;
      end;
    end;
  end;
end;
{$WARNINGS ON}

function TKernelResampler.GetWidth: TFloat;
begin
  Result := Kernel.GetWidth;
end;

procedure TKernelResampler.SetKernelMode(const Value: TKernelMode);
begin
  if FKernelMode <> Value then
  begin
    FKernelMode := Value;
    Changed;
  end;
end;

procedure TKernelResampler.SetTableSize(Value: Integer);
begin
  if Value < 2 then Value := 2;
  if FTableSize <> Value then
  begin
    FTableSize := Value;
    Changed;
  end;
end;

procedure TKernelResampler.FinalizeSampling;
begin
  if FKernelMode in [kmTableNearest, kmTableLinear] then
    FWeightTable.Free;
  inherited;
end;

procedure TKernelResampler.PrepareSampling;
var
  I, J, W, Weight, Dev: Integer;
  Fraction: TFloat;
  KernelPtr: PKernelEntry;
begin
  inherited;
  FOuterColor := Bitmap.OuterColor;
  W := Ceil(FKernel.GetWidth);
  if FKernelMode in [kmTableNearest, kmTableLinear] then
  begin
    FWeightTable := TIntegerMap.Create(W * 2 + 1, FTableSize + 1);
    for I := 0 to FTableSize do
    begin
      Fraction := I / (FTableSize - 1);
      KernelPtr :=  @FWeightTable.ValPtr[W - MAX_KERNEL_WIDTH, I]^;
      Dev := - 256;
      for J := -W to W do
      begin
        Weight := Round(FKernel.Filter(J + Fraction) * 256);
        KernelPtr[J] := Weight;
        Inc(Dev, Weight);
      end;
      Dec(KernelPtr[0], Dev);
    end;
  end;
end;


//------------------------------------------------------------------------------
//
//      TNearestResampler
//
//------------------------------------------------------------------------------
function TNearestResampler.GetSampleInt(X, Y: Integer): TColor32;
begin
  Result := FGetSampleInt(X, Y);
end;

function TNearestResampler.GetSampleFixed(X, Y: TFixed): TColor32;
begin
  Result := FGetSampleInt(FixedRound(X), FixedRound(Y));
end;

function TNearestResampler.GetSampleFloat(X, Y: TFloat): TColor32;
begin
  Result := FGetSampleInt(Round(X), Round(Y));
end;

function TNearestResampler.GetWidth: TFloat;
begin
  Result := 1;
end;

function TNearestResampler.GetPixelTransparentEdge(X,Y: Integer): TColor32;
var
  I, J: Integer;
begin
  with Bitmap, Bitmap.ClipRect do
  begin
    I := Clamp(X, Left, Right - 1);
    J := Clamp(Y, Top, Bottom - 1);
    Result := Pixel[I, J];
    if (I <> X) or (J <> Y) then
      Result := Result and $00FFFFFF;
  end;
end;

procedure TNearestResampler.PrepareSampling;
begin
  inherited;
  case PixelAccessMode of
    pamUnsafe: FGetSampleInt := TCustomBitmap32Access(Bitmap).GetPixel;
    pamSafe: FGetSampleInt := TCustomBitmap32Access(Bitmap).GetPixelS;
    pamWrap: FGetSampleInt := TCustomBitmap32Access(Bitmap).GetPixelW;
    pamTransparentEdge: FGetSampleInt := GetPixelTransparentEdge;
  end;
end;

procedure TNearestResampler.Resample(
  Dst: TCustomBitmap32; DstRect: TRect; DstClip: TRect;
  Src: TCustomBitmap32; SrcRect: TRect;
  CombineOp: TDrawMode; CombineCallBack: TPixelCombineEvent);
begin
  StretchNearest(Dst, DstRect, DstClip, Src, SrcRect, CombineOp, CombineCallBack)
end;


//------------------------------------------------------------------------------
//
//      TLinearResampler
//
//------------------------------------------------------------------------------
constructor TLinearResampler.Create;
begin
  inherited;
  FLinearKernel := TLinearKernel.Create;
end;

destructor TLinearResampler.Destroy;
begin
  FLinearKernel.Free;
  inherited Destroy;
end;

function TLinearResampler.GetSampleFixed(X, Y: TFixed): TColor32;
begin
  Result := FGetSampleFixed(X, Y);
end;

function TLinearResampler.GetSampleFloat(X, Y: TFloat): TColor32;
begin
  Result := FGetSampleFixed(Round(X * FixedOne), Round(Y * FixedOne));
end;

function TLinearResampler.GetPixelTransparentEdge(X, Y: TFixed): TColor32;
var
  PixelX, PixelY, X1, X2, Y1, Y2, WeightX, EdgeX, EdgeY: TFixed;
  C1, C2, C3, C4: TColor32;
  PSrc: PColor32Array;
begin
  EdgeX := Bitmap.ClipRect.Right - 1;
  EdgeY := Bitmap.ClipRect.Bottom - 1;

  PixelX := TFixedRec(X).Int;
  PixelY := TFixedRec(Y).Int;

  if (PixelX >= Bitmap.ClipRect.Left) and (PixelY >= Bitmap.ClipRect.Top) and (PixelX < EdgeX) and (PixelY < EdgeY) then
  begin //Safe
    Result := TCustomBitmap32Access(Bitmap).GET_T256(X shr 8, Y shr 8);
  end
  else
  if (PixelX >= Bitmap.ClipRect.Left - 1) and (PixelY >= Bitmap.ClipRect.Top - 1) and (PixelX <= EdgeX) and (PixelY <= EdgeY) then
  begin //Near edge, on edge or outside

    X1 := Clamp(PixelX, EdgeX);
    X2 := Clamp(PixelX + Sign(X), EdgeX);
    Y1 := Clamp(PixelY, EdgeY) * Bitmap.Width;
    Y2 := Clamp(PixelY + Sign(Y), EdgeY) * Bitmap.Width;

    PSrc := @Bitmap.Bits[0];
    C1 := PSrc[X1 + Y1];
    C2 := PSrc[X2 + Y1];
    C3 := PSrc[X1 + Y2];
    C4 := PSrc[X2 + Y2];

    if X <= Fixed(Bitmap.ClipRect.Left) then
    begin
      C1 := C1 and $00FFFFFF;
      C3 := C3 and $00FFFFFF;
    end else
    if PixelX = EdgeX then
    begin
      C2 := C2 and $00FFFFFF;
      C4 := C4 and $00FFFFFF;
    end;

    if Y <= Fixed(Bitmap.ClipRect.Top) then
    begin
      C1 := C1 and $00FFFFFF;
      C2 := C2 and $00FFFFFF;
    end else
    if PixelY = EdgeY then
    begin
      C3 := C3 and $00FFFFFF;
      C4 := C4 and $00FFFFFF;
    end;

    WeightX := ((X shr 8) and $FF) xor $FF;

    Result := CombineReg(CombineReg(C1, C2, WeightX),
                         CombineReg(C3, C4, WeightX),
                         ((Y shr 8) and $FF) xor $FF);
  end
  else
    Result := 0; //Nothing really makes sense here, return zero
end;

procedure TLinearResampler.PrepareSampling;
begin
  inherited;
  case PixelAccessMode of
    pamUnsafe: FGetSampleFixed := TCustomBitmap32Access(Bitmap).GetPixelX;
    pamSafe: FGetSampleFixed := TCustomBitmap32Access(Bitmap).GetPixelXS;
    pamWrap: FGetSampleFixed := TCustomBitmap32Access(Bitmap).GetPixelXW;
    pamTransparentEdge: FGetSampleFixed := GetPixelTransparentEdge;
  end;
end;

function TLinearResampler.GetWidth: TFloat;
begin
  Result := 1;
end;

procedure TLinearResampler.Resample(
  Dst: TCustomBitmap32; DstRect: TRect; DstClip: TRect;
  Src: TCustomBitmap32; SrcRect: TRect;
  CombineOp: TDrawMode; CombineCallBack: TPixelCombineEvent);
var
  SrcW, SrcH: TFloat;
  DstW, DstH: Integer;
begin
  SrcW := SrcRect.Right - SrcRect.Left;
  SrcH := SrcRect.Bottom - SrcRect.Top;
  DstW := DstRect.Right - DstRect.Left;
  DstH := DstRect.Bottom - DstRect.Top;
  if (DstW > SrcW) and (DstH > SrcH) and (SrcW > 1) and (SrcH > 1) then
    StretchHorzStretchVertLinear(Dst, DstRect, DstClip, Src, SrcRect, CombineOp,
      CombineCallBack)
  else
    GR32_Resamplers.Resample(Dst, DstRect, DstClip, Src, SrcRect, FLinearKernel,
      CombineOp, CombineCallBack);
end;

procedure TDraftResampler.Resample(
  Dst: TCustomBitmap32; DstRect: TRect; DstClip: TRect;
  Src: TCustomBitmap32; SrcRect: TRect;
  CombineOp: TDrawMode; CombineCallBack: TPixelCombineEvent);
begin
  DraftResample(Dst, DstRect, DstClip, Src, SrcRect, FLinearKernel, CombineOp,
    CombineCallBack)
end;


//------------------------------------------------------------------------------
//
//      TTransformer
//
//------------------------------------------------------------------------------
function TTransformer.GetSampleInt(X, Y: Integer): TColor32;
var
  U, V: TFixed;
begin
  FTransformFixed(X * FixedOne + FixedHalf, Y * FixedOne + FixedHalf, U, V);
  Result := FGetSampleFixed(U - FixedHalf, V - FixedHalf);
end;

function TTransformer.GetSampleFixed(X, Y: TFixed): TColor32;
var
  U, V: TFixed;
begin
  FTransformFixed(X + FixedHalf, Y + FixedHalf, U, V);
  Result := FGetSampleFixed(U - FixedHalf, V - FixedHalf);
end;

function TTransformer.GetSampleFloat(X, Y: TFloat): TColor32;
var
  U, V: TFloat;
begin
  FTransformFloat(X + 0.5, Y + 0.5, U, V);
  Result := FGetSampleFloat(U - 0.5, V - 0.5);
end;

constructor TTransformer.Create(ASampler: TCustomSampler; ATransformation: TTransformation; AReverse: boolean);
begin
  inherited Create(ASampler);
  FTransformation := ATransformation;
  FReverse := AReverse;
end;

procedure TTransformer.PrepareSampling;
begin
  inherited;

  if (FTransformation = nil) then
    raise ETransformerException.Create(STransformationNil);

  if (FReverse) then
  begin
    FTransformInt := TTransformationAccess(FTransformation).ReverseTransformInt;
    FTransformFixed := TTransformationAccess(FTransformation).ReverseTransformFixed;
    FTransformFloat := TTransformationAccess(FTransformation).ReverseTransformFloat;
  end else
  begin
    FTransformInt := TTransformationAccess(FTransformation).TransformInt;
    FTransformFixed := TTransformationAccess(FTransformation).TransformFixed;
    FTransformFloat := TTransformationAccess(FTransformation).TransformFloat;
  end;

  if not TTransformationAccess(FTransformation).TransformValid then
    TTransformationAccess(FTransformation).PrepareTransform;
end;

function TTransformer.GetSampleBounds: TFloatRect;
begin
  GR32.IntersectRect(Result, inherited GetSampleBounds, FTransformation.SrcRect);
  Result := FTransformation.GetTransformedBounds(Result);
end;

function TTransformer.HasBounds: Boolean;
begin
  Result := FTransformation.HasTransformedBounds and inherited HasBounds;
end;


//------------------------------------------------------------------------------
//
//      TSuperSampler
//
//------------------------------------------------------------------------------
constructor TSuperSampler.Create(Sampler: TCustomSampler);
begin
  inherited Create(Sampler);
  FSamplingX := 4;
  FSamplingY := 4;
  SamplingX := 4;
  SamplingY := 4;
end;

function TSuperSampler.GetSampleFixed(X, Y: TFixed): TColor32;
var
  I, J: Integer;
  dX, dY, tX: TFixed;
  Buffer: TBufferEntry;
begin
  Buffer := Default(TBufferEntry);
  tX := X + FOffsetX;
  Inc(Y, FOffsetY);
  dX := FDistanceX;
  dY := FDistanceY;
  for J := 1 to FSamplingY do
  begin
    X := tX;
    for I := 1 to FSamplingX do
    begin
      IncBuffer(Buffer, FGetSampleFixed(X, Y));
      Inc(X, dX);
    end;
    Inc(Y, dY);
  end;
  MultiplyBuffer(Buffer, FScale);
  Result := BufferToColor32(Buffer, 16);
end;

procedure TSuperSampler.SetSamplingX(const Value: TSamplingRange);
begin
  FSamplingX := Value;
  FDistanceX := Fixed(1 / Value);
  FOffsetX := Fixed(((1 / Value) - 1) * 0.5);     // replaced "/2" by "*0.5"
  FScale := Fixed(1 / (FSamplingX * FSamplingY));
end;

procedure TSuperSampler.SetSamplingY(const Value: TSamplingRange);
begin
  FSamplingY := Value;
  FDistanceY := Fixed(1 / Value);
  FOffsetY := Fixed(((1 / Value) - 1) * 0.5);     // replaced "/2" by "*0.5"
  FScale := Fixed(1 / (FSamplingX * FSamplingY));
end;


//------------------------------------------------------------------------------
//
//      TAdaptiveSuperSampler
//
//------------------------------------------------------------------------------
function TAdaptiveSuperSampler.CompareColors(C1, C2: TColor32): Boolean;
var
  Diff: TColor32Entry;
begin
  Diff.ARGB := ColorDifference(C1, C2);
  Result := FTolerance < Diff.R + Diff.G + Diff.B;
end;

constructor TAdaptiveSuperSampler.Create(Sampler: TCustomSampler);
begin
  inherited Create(Sampler);
  Level := 4;
  Tolerance := 256;
end;

function TAdaptiveSuperSampler.DoRecurse(X, Y, Offset: TFixed; const A, B,
  C, D, E: TColor32): TColor32;
var
  C1, C2, C3, C4: TColor32;
begin
  C1 := QuadrantColor(A, E, X - Offset, Y - Offset, Offset, RecurseAC);
  C2 := QuadrantColor(B, E, X + Offset, Y - Offset, Offset, RecurseBD);
  C3 := QuadrantColor(E, C, X + Offset, Y + Offset, Offset, RecurseAC);
  C4 := QuadrantColor(E, D, X - Offset, Y + Offset, Offset, RecurseBD);
  Result := ColorAverage(ColorAverage(C1, C2), ColorAverage(C3, C4));
end;

function TAdaptiveSuperSampler.GetSampleFixed(X, Y: TFixed): TColor32;
var
  A, B, C, D, E: TColor32;
const
  FIXED_HALF = 32768;
begin
  A := FGetSampleFixed(X - FIXED_HALF, Y - FIXED_HALF);
  B := FGetSampleFixed(X + FIXED_HALF, Y - FIXED_HALF);
  C := FGetSampleFixed(X + FIXED_HALF, Y + FIXED_HALF);
  D := FGetSampleFixed(X - FIXED_HALF, Y + FIXED_HALF);
  E := FGetSampleFixed(X, Y);
  Result := Self.DoRecurse(X, Y, 16384, A, B, C, D, E);
end;

function TAdaptiveSuperSampler.QuadrantColor(const C1, C2: TColor32; X, Y,
  Offset: TFixed; Proc: TRecurseProc): TColor32;
begin
  if CompareColors(C1, C2) and (Offset >= FMinOffset) then
    Result := Proc(X, Y, Offset, C1, C2)
  else
    Result := ColorAverage(C1, C2);
end;

function TAdaptiveSuperSampler.RecurseAC(X, Y, Offset: TFixed; const A,
  C: TColor32): TColor32;
var
  B, D, E: TColor32;
begin
  B := FGetSampleFixed(X + Offset, Y - Offset);
  D := FGetSampleFixed(X - Offset, Y + Offset);
  E := FGetSampleFixed(X, Y);
  Result := DoRecurse(X, Y, Offset shr 1, A, B, C, D, E);
end;

function TAdaptiveSuperSampler.RecurseBD(X, Y, Offset: TFixed; const B,
  D: TColor32): TColor32;
var
  A, C, E: TColor32;
begin
  A := FGetSampleFixed(X - Offset, Y - Offset);
  C := FGetSampleFixed(X + Offset, Y + Offset);
  E := FGetSampleFixed(X, Y);
  Result := DoRecurse(X, Y, Offset shr 1, A, B, C, D, E);
end;

procedure TAdaptiveSuperSampler.SetLevel(const Value: Integer);
begin
  FLevel := Value;
  FMinOffset := Fixed(1 / (1 shl Value));
end;


//------------------------------------------------------------------------------
//
//      TPatternSampler
//
//------------------------------------------------------------------------------
destructor TPatternSampler.Destroy;
begin
  FPattern := nil;
  inherited;
end;

function TPatternSampler.GetSampleFixed(X, Y: TFixed): TColor32;
var
  Points: TArrayOfFixedPoint;
  P: PFixedPoint;
  I, PY: Integer;
  Buffer: TBufferEntry;
  GetSample: TGetSampleFixed;
  WrapProcHorz: TWrapProc;
begin
  GetSample := FSampler.GetSampleFixed;
  PY := WrapProcVert(TFixedRec(Y).Int, High(FPattern));
  I := High(FPattern[PY]);
  WrapProcHorz := GetOptimalWrap(I);
  Points := FPattern[PY][WrapProcHorz(TFixedRec(X).Int, I)];
  Buffer := Default(TBufferEntry);
  P := @Points[0];
  for I := 0 to High(Points) do
  begin
    IncBuffer(Buffer, GetSample(P.X + X, P.Y + Y));
    Inc(P);
  end;
  MultiplyBuffer(Buffer, FixedOne div Length(Points));
  Result := BufferToColor32(Buffer, 16);
end;

procedure TPatternSampler.SetPattern(const Value: TFixedSamplePattern);
begin
  if (Value <> nil) then
  begin
    FPattern := Value;
    WrapProcVert := GetOptimalWrap(High(FPattern));
  end;
end;


//------------------------------------------------------------------------------
//
//      CreateJitteredPattern
//
//------------------------------------------------------------------------------
function JitteredPattern(XRes, YRes: Integer): TArrayOfFixedPoint;
var
  I, J: Integer;
begin
  SetLength(Result, XRes * YRes);
  for I := 0 to XRes - 1 do
    for J := 0 to YRes - 1 do
      with Result[I + J * XRes] do
        begin
          X := (Random(65536) + I * 65536) div XRes - 32768;
          Y := (Random(65536) + J * 65536) div YRes - 32768;
        end;
end;

function CreateJitteredPattern(TileWidth, TileHeight, SamplesX, SamplesY: Integer): TFixedSamplePattern;
var
  I, J: Integer;
begin
  SetLength(Result, TileHeight, TileWidth);
  for I := 0 to TileWidth - 1 do
    for J := 0 to TileHeight - 1 do
      Result[J][I] := JitteredPattern(SamplesX, SamplesY);
end;


//------------------------------------------------------------------------------
//
//      TNestedSampler
//
//------------------------------------------------------------------------------
procedure TNestedSampler.AssignTo(Dst: TPersistent);
begin
  if Dst is TNestedSampler then
    SmartAssign(Self, Dst)
  else
    inherited;
end;

constructor TNestedSampler.Create(ASampler: TCustomSampler);
begin
  inherited Create;
  Sampler := ASampler;
end;

procedure TNestedSampler.FinalizeSampling;
begin
  if (FSampler = nil) then
    raise ENestedException.Create(SSamplerNil);
  FSampler.FinalizeSampling;
end;

{$WARNINGS OFF}
function TNestedSampler.GetSampleBounds: TFloatRect;
begin
  if (FSampler = nil) then
    raise ENestedException.Create(SSamplerNil);
  Result := FSampler.GetSampleBounds;
end;

function TNestedSampler.HasBounds: Boolean;
begin
  if (FSampler = nil) then
    raise ENestedException.Create(SSamplerNil);
  Result := FSampler.HasBounds;
end;
{$WARNINGS ON}

procedure TNestedSampler.PrepareSampling;
begin
  if (FSampler = nil) then
    raise ENestedException.Create(SSamplerNil);
  FSampler.PrepareSampling;
end;

procedure TNestedSampler.SetSampler(const Value: TCustomSampler);
begin
  FSampler := Value;
  if (Value <> nil) then
  begin
    FGetSampleInt := FSampler.GetSampleInt;
    FGetSampleFixed := FSampler.GetSampleFixed;
    FGetSampleFloat := FSampler.GetSampleFloat;
  end;
end;


//------------------------------------------------------------------------------
//
//      TKernelSampler
//
//------------------------------------------------------------------------------
function TKernelSampler.ConvertBuffer(var Buffer: TBufferEntry): TColor32;
begin
  Buffer.A := Constrain(Buffer.A, 0, $FFFF);
  Buffer.R := Constrain(Buffer.R, 0, $FFFF);
  Buffer.G := Constrain(Buffer.G, 0, $FFFF);
  Buffer.B := Constrain(Buffer.B, 0, $FFFF);

  Result := BufferToColor32(Buffer, 8);
end;

constructor TKernelSampler.Create(ASampler: TCustomSampler);
begin
  inherited;
  FKernel := TIntegerMap.Create;
  FStartEntry := Default(TBufferEntry);
end;

destructor TKernelSampler.Destroy;
begin
  FKernel.Free;
  inherited;
end;

function TKernelSampler.GetSampleFixed(X, Y: TFixed): TColor32;
var
  I, J: Integer;
  Buffer: TBufferEntry;
begin
  X := X + FCenterX shl 16;
  Y := Y + FCenterY shl 16;
  Buffer := FStartEntry;
  for I := 0 to FKernel.Width - 1 do
    for J := 0 to FKernel.Height - 1 do
      UpdateBuffer(Buffer, FGetSampleFixed(X - I shl 16, Y - J shl 16), FKernel[I, J]);

  Result := ConvertBuffer(Buffer);
end;

function TKernelSampler.GetSampleInt(X, Y: Integer): TColor32;
var
  I, J: Integer;
  Buffer: TBufferEntry;
begin
  X := X + FCenterX;
  Y := Y + FCenterY;
  Buffer := FStartEntry;
  for I := 0 to FKernel.Width - 1 do
    for J := 0 to FKernel.Height - 1 do
      UpdateBuffer(Buffer, FGetSampleInt(X - I, Y - J), FKernel[I, J]);

  Result := ConvertBuffer(Buffer);
end;

procedure TKernelSampler.SetKernel(const Value: TIntegerMap);
begin
  FKernel.Assign(Value);
end;


//------------------------------------------------------------------------------
//
//      TConvolver
//
//------------------------------------------------------------------------------
procedure TConvolver.UpdateBuffer(var Buffer: TBufferEntry; Color: TColor32;
  Weight: Integer);
begin
  with TColor32Entry(Color) do
  begin
    Inc(Buffer.A, A * Weight);
    Inc(Buffer.R, R * Weight);
    Inc(Buffer.G, G * Weight);
    Inc(Buffer.B, B * Weight);
  end;
end;


//------------------------------------------------------------------------------
//
//      TDilater
//
//------------------------------------------------------------------------------
procedure TDilater.UpdateBuffer(var Buffer: TBufferEntry; Color: TColor32;
  Weight: Integer);
begin
  with TColor32Entry(Color) do
  begin
    Buffer.A := Max(Buffer.A, A + Weight);
    Buffer.R := Max(Buffer.R, R + Weight);
    Buffer.G := Max(Buffer.G, G + Weight);
    Buffer.B := Max(Buffer.B, B + Weight);
  end;
end;


//------------------------------------------------------------------------------
//
//      TEroder
//
//------------------------------------------------------------------------------
constructor TEroder.Create(ASampler: TCustomSampler);
const
  START_ENTRY: TBufferEntry = (B: $FFFF; G: $FFFF; R: $FFFF; A: $FFFF);
begin
  inherited;
  FStartEntry := START_ENTRY;
end;

procedure TEroder.UpdateBuffer(var Buffer: TBufferEntry; Color: TColor32;
  Weight: Integer);
begin
  with TColor32Entry(Color) do
  begin
    Buffer.A := Min(Buffer.A, A - Weight);
    Buffer.R := Min(Buffer.R, R - Weight);
    Buffer.G := Min(Buffer.G, G - Weight);
    Buffer.B := Min(Buffer.B, B - Weight);
  end;
end;


//------------------------------------------------------------------------------
//
//      TExpander
//
//------------------------------------------------------------------------------
procedure TExpander.UpdateBuffer(var Buffer: TBufferEntry; Color: TColor32;
  Weight: Integer);
begin
  with TColor32Entry(Color) do
  begin
    Buffer.A := Max(Buffer.A, A * Weight);
    Buffer.R := Max(Buffer.R, R * Weight);
    Buffer.G := Max(Buffer.G, G * Weight);
    Buffer.B := Max(Buffer.B, B * Weight);
  end;
end;


//------------------------------------------------------------------------------
//
//      TContracter
//
//------------------------------------------------------------------------------
function TContracter.GetSampleFixed(X, Y: TFixed): TColor32;
begin
  Result := ColorSub(FMaxWeight, inherited GetSampleFixed(X, Y));
end;

function TContracter.GetSampleInt(X, Y: Integer): TColor32;
begin
  Result := ColorSub(FMaxWeight, inherited GetSampleInt(X, Y));
end;

procedure TContracter.PrepareSampling;
var
  I, J, W: Integer;
begin
  W := Low(Integer);
  for I := 0 to FKernel.Width - 1 do
    for J := 0 to FKernel.Height - 1 do
      W := Max(W, FKernel[I, J]);
  if W > 255 then
    W := 255;
  FMaxWeight := Gray32(W, W);
end;

procedure TContracter.UpdateBuffer(var Buffer: TBufferEntry; Color: TColor32;
  Weight: Integer);
begin
  inherited UpdateBuffer(Buffer, Color xor $FFFFFFFF, Weight);
end;


//------------------------------------------------------------------------------
//
//      TMorphologicalSampler
//
//------------------------------------------------------------------------------
function TMorphologicalSampler.ConvertBuffer(
  var Buffer: TBufferEntry): TColor32;
begin
  Buffer.A := Constrain(Buffer.A, 0, $FF);
  Buffer.R := Constrain(Buffer.R, 0, $FF);
  Buffer.G := Constrain(Buffer.G, 0, $FF);
  Buffer.B := Constrain(Buffer.B, 0, $FF);

  with TColor32Entry(Result) do
  begin
    A := Buffer.A;
    R := Buffer.R;
    G := Buffer.G;
    B := Buffer.B;
  end;
end;


//------------------------------------------------------------------------------
//
//      TSelectiveConvolver
//
//------------------------------------------------------------------------------
function TSelectiveConvolver.ConvertBuffer(var Buffer: TBufferEntry): TColor32;
begin
  with TColor32Entry(Result) do
  begin
    A := Buffer.A div FWeightSum.A;
    R := Buffer.R div FWeightSum.R;
    G := Buffer.G div FWeightSum.G;
    B := Buffer.B div FWeightSum.B;
  end;
end;

constructor TSelectiveConvolver.Create(ASampler: TCustomSampler);
begin
  inherited;
  FDelta := 30;
end;

function TSelectiveConvolver.GetSampleFixed(X, Y: TFixed): TColor32;
begin
  FRefColor := FGetSampleFixed(X, Y);
  FWeightSum := Default(TBufferEntry);
  Result := inherited GetSampleFixed(X, Y);
end;

function TSelectiveConvolver.GetSampleInt(X, Y: Integer): TColor32;
begin
  FRefColor := FGetSampleInt(X, Y);
  FWeightSum := Default(TBufferEntry);
  Result := inherited GetSampleInt(X, Y);
end;

procedure TSelectiveConvolver.UpdateBuffer(var Buffer: TBufferEntry;
  Color: TColor32; Weight: Integer);
begin
  with TColor32Entry(Color) do
  begin
    if Abs(TColor32Entry(FRefColor).A - A) <= FDelta then
    begin
      Inc(Buffer.A, A * Weight);
      Inc(FWeightSum.A, Weight);
    end;
    if Abs(TColor32Entry(FRefColor).R - R) <= FDelta then
    begin
      Inc(Buffer.R, R * Weight);
      Inc(FWeightSum.R, Weight);
    end;
    if Abs(TColor32Entry(FRefColor).G - G) <= FDelta then
    begin
      Inc(Buffer.G, G * Weight);
      Inc(FWeightSum.G, Weight);
    end;
    if Abs(TColor32Entry(FRefColor).B - B) <= FDelta then
    begin
      Inc(Buffer.B, B * Weight);
      Inc(FWeightSum.B, Weight);
    end;
  end;
end;

//------------------------------------------------------------------------------
//
//      Registration routines
//
//------------------------------------------------------------------------------
procedure RegisterResampler(ResamplerClass: TCustomResamplerClass);
begin
  if (ResamplerList = nil) then
    ResamplerList := TClassList.Create;
  ResamplerList.Add(ResamplerClass);
end;

procedure RegisterKernel(KernelClass: TCustomKernelClass);
begin
  if (KernelList = nil) then
    KernelList := TClassList.Create;
  KernelList.Add(KernelClass);
end;


//------------------------------------------------------------------------------
//
//      Bindings
//
//------------------------------------------------------------------------------
var
  ResamplersRegistry: TFunctionRegistry;

procedure RegisterBindings;
begin
  ResamplersRegistry := NewRegistry('GR32_Resamplers bindings');
  ResamplersRegistry.RegisterBinding(@@BlockAverage);
  ResamplersRegistry.RegisterBinding(@@Interpolator);

  ResamplersRegistry.ADD(@@BlockAverage, @BlockAverage_Pas, [isPascal]);
  ResamplersRegistry.ADD(@@Interpolator, @Interpolator_Pas, [isPascal]);
{$IFNDEF PUREPASCAL}
{$IFNDEF OMIT_SSE2}
  ResamplersRegistry.ADD(@@BlockAverage, @BlockAverage_SSE2, [isSSE2]);
  ResamplersRegistry.ADD(@@Interpolator, @Interpolator_SSE2, [isSSE2]);
{$ENDIF}
{$ENDIF}
  ResamplersRegistry.RebindAll;
end;

//------------------------------------------------------------------------------

initialization
  RegisterBindings;

  { Register resamplers }
  RegisterResampler(TNearestResampler);
  RegisterResampler(TLinearResampler);
  RegisterResampler(TDraftResampler);
  RegisterResampler(TKernelResampler);

  { Register kernels }
  RegisterKernel(TBoxKernel);
  RegisterKernel(TLinearKernel);
  RegisterKernel(TCosineKernel);
  RegisterKernel(TSplineKernel);
  RegisterKernel(TCubicKernel);
  RegisterKernel(TMitchellKernel);
  RegisterKernel(TAlbrechtKernel);
  RegisterKernel(TLanczosKernel);
  RegisterKernel(TGaussianKernel);
  RegisterKernel(TBlackmanKernel);
  RegisterKernel(THannKernel);
  RegisterKernel(THammingKernel);
  RegisterKernel(TSinshKernel);
  RegisterKernel(THermiteKernel);

finalization
  ResamplerList.Free;
  KernelList.Free;

end.
